<!DOCTYPE html>
<html lang=en>
<head>
    <meta charset="utf-8">
    
    <title>The Old Man and the Sea</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
    <meta name="description" content="All any predator can do is to keep on trying.">
<meta property="og:type" content="website">
<meta property="og:title" content="The Old Man and the Sea">
<meta property="og:url" content="http://yoursite.com/page/2/index.html">
<meta property="og:site_name" content="The Old Man and the Sea">
<meta property="og:description" content="All any predator can do is to keep on trying.">
<meta property="og:locale" content="en">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="The Old Man and the Sea">
<meta name="twitter:description" content="All any predator can do is to keep on trying.">
    

    
        <link rel="alternate" href="/" title="The Old Man and the Sea" type="application/atom+xml" />
    

    

    <link rel="stylesheet" href="/libs/font-awesome/css/font-awesome.min.css">
    <link rel="stylesheet" href="/libs/open-sans/styles.css">
    <link rel="stylesheet" href="/libs/source-code-pro/styles.css">

    <link rel="stylesheet" href="/css/style.css">

    <script src="/libs/jquery/2.1.3/jquery.min.js"></script>
    
    
        <link rel="stylesheet" href="/libs/lightgallery/css/lightgallery.min.css">
    
    
        <link rel="stylesheet" href="/libs/justified-gallery/justifiedGallery.min.css">
    
    
    
    


</head>

<body>
    <div id="container">
        <header id="header">
    <div id="header-main" class="header-inner">
        <div class="outer">
            <a href="/" id="logo">
                
                <span class="site-title">The Old Man and the Sea</span>
            </a>
            <nav id="main-nav">
                
                    <a class="main-nav-link" href="/.">Home</a>
                
                    <a class="main-nav-link" href="/archives">Archives</a>
                
                    <a class="main-nav-link" href="/categories">Categories</a>
                
                    <a class="main-nav-link" href="/tags">Tags</a>
                
                    <a class="main-nav-link" href="/about">About</a>
                
            </nav>
            
                
                <nav id="sub-nav">
                    <div class="profile" id="profile-nav">
                        <a id="profile-anchor" href="javascript:;">
                            <img class="avatar" src="/uploads/images/avert.png" />
                            <i class="fa fa-caret-down"></i>
                        </a>
                    </div>
                </nav>
            
            <div id="search-form-wrap">

    <form class="search-form">
        <input type="text" class="ins-search-input search-form-input" placeholder="Search" />
        <button type="submit" class="search-form-submit"></button>
    </form>
    <div class="ins-search">
    <div class="ins-search-mask"></div>
    <div class="ins-search-container">
        <div class="ins-input-wrapper">
            <input type="text" class="ins-search-input" placeholder="Type something..." />
            <span class="ins-close ins-selectable"><i class="fa fa-times-circle"></i></span>
        </div>
        <div class="ins-section-wrapper">
            <div class="ins-section-container"></div>
        </div>
    </div>
</div>
<script>
(function (window) {
    var INSIGHT_CONFIG = {
        TRANSLATION: {
            POSTS: 'Posts',
            PAGES: 'Pages',
            CATEGORIES: 'Categories',
            TAGS: 'Tags',
            UNTITLED: '(Untitled)',
        },
        ROOT_URL: '/',
        CONTENT_URL: '/content.json',
    };
    window.INSIGHT_CONFIG = INSIGHT_CONFIG;
})(window);
</script>
<script src="/js/insight.js"></script>

</div>
        </div>
    </div>
    <div id="main-nav-mobile" class="header-sub header-inner">
        <table class="menu outer">
            <tr>
                
                    <td><a class="main-nav-link" href="/.">Home</a></td>
                
                    <td><a class="main-nav-link" href="/archives">Archives</a></td>
                
                    <td><a class="main-nav-link" href="/categories">Categories</a></td>
                
                    <td><a class="main-nav-link" href="/tags">Tags</a></td>
                
                    <td><a class="main-nav-link" href="/about">About</a></td>
                
                <td>
                    
    <div class="search-form">
        <input type="text" class="ins-search-input search-form-input" placeholder="Search" />
    </div>

                </td>
            </tr>
        </table>
    </div>
</header>

        <div class="outer">
            
                

<aside id="profile">
    <div class="inner profile-inner">
        <div class="base-info profile-block">
            <img id="avatar" src="/uploads/images/avert.png" />
            <h2 id="name">Ernest Chang</h2>
            <h3 id="title">Developer</h3>
            <span id="location"><i class="fa fa-map-marker"></i>Hangzhou, China</span>
            <a id="follow" target="_blank" href="https://github.com/Ernestchang/">FOLLOW</a>
        </div>
        <div class="article-info profile-block">
            <div class="article-info-block">
                22
                <span>posts</span>
            </div>
            <div class="article-info-block">
                9
                <span>tags</span>
            </div>
        </div>
        
        <div class="profile-block social-links">
            <table>
                <tr>
                    
                    
                    <td>
                        <a href="https://github.com/Ernestchang/" target="_blank" title="github" class=tooltip>
                            <i class="fa fa-github"></i>
                        </a>
                    </td>
                    
                    <td>
                        <a href="/" target="_blank" title="twitter" class=tooltip>
                            <i class="fa fa-twitter"></i>
                        </a>
                    </td>
                    
                    <td>
                        <a href="/" target="_blank" title="facebook" class=tooltip>
                            <i class="fa fa-facebook"></i>
                        </a>
                    </td>
                    
                    <td>
                        <a href="/" target="_blank" title="dribbble" class=tooltip>
                            <i class="fa fa-dribbble"></i>
                        </a>
                    </td>
                    
                    <td>
                        <a href="/" target="_blank" title="rss" class=tooltip>
                            <i class="fa fa-rss"></i>
                        </a>
                    </td>
                    
                </tr>
            </table>
        </div>
        
    </div>
</aside>

            
            <section id="main">
    <article id="post-android/books/《Android开发艺术探索》Chap3_View的事件体系" class="article article-type-post" itemscope itemprop="blogPost">
    <div class="article-inner">
        
        
            <header class="article-header">
                
    
        <h1 itemprop="name">
            <a class="article-title" href="/2017/03/15/android/books/《Android开发艺术探索》Chap3_View的事件体系/">《Android开发艺术探索》Chap3_View的事件体系</a>
        </h1>
    

                
                    <div class="article-meta">
                        
    <div class="article-date">
        <i class="fa fa-calendar"></i>
        <a href="/2017/03/15/android/books/《Android开发艺术探索》Chap3_View的事件体系/">
            <time datetime="2017-03-15T10:02:59.000Z" itemprop="datePublished">2017-03-15</time>
        </a>
    </div>


                        
    <div class="article-category">
    	<i class="fa fa-folder"></i>
        <a class="article-category-link" href="/categories/android/">android</a><i class="fa fa-angle-right"></i><a class="article-category-link" href="/categories/android/books/">books</a><i class="fa fa-angle-right"></i><a class="article-category-link" href="/categories/android/books/《Android开发艺术探索》/">《Android开发艺术探索》</a>
    </div>

                        
    <div class="article-tag">
        <i class="fa fa-tag"></i>
        <a class="tag-link" href="/tags/android/">android</a>, <a class="tag-link" href="/tags/books/">books</a>
    </div>

                    </div>
                
            </header>
        
        
        <div class="article-entry" itemprop="articleBody">
        
            
            <p>##View的基础知识</p>
<ul>
<li>什么是View</li>
</ul>
<p>View是Android中所有控件的基类，View是一种界面层的控件的一种抽象，它代表了一个控件，在Android设计中，ViewGroup也继承了View，这就意味着View本身就可以是单个控件也可以是多个控件组成的一组控件，通过这种关系就形成了View树的结构。</p>
<ul>
<li>View的位置参数</li>
</ul>
<p>view的位置主要由它的四个顶点来决定，分别对应于View的四个属性：top、left、right、bottom，其中top是左上角纵坐标，left是左上角横坐标，right是右下角横坐标，bottom是右下角纵坐标</p>
<p>View的宽高和坐标的关系：</p>
<p>width = right - left;</p>
<p>height = bottom - top;</p>
<p>如何得到这四个参数：</p>
<p>Left = getLeft();</p>
<p>Right = getRight();</p>
<p>Top = getTop();</p>
<p>Bottom = getBottom();</p>
<p>从Android 3.0开始，view增加了x、y、translationX、translationY四个参数，这几个参数也是相对于父容器的坐标。x和y是左上角的坐标，而translationX和translationY是view左上角相对于父容器的偏移量，默认值都是0。</p>
<p>x = left + translationX</p>
<p>y = top + translationY</p>
<ul>
<li>MotionEvent和TouchSlop</li>
</ul>
<p>MotionEvent:</p>
<p>在手指触摸屏幕后所产生的一系列事件中，典型的时间类型有：</p>
<p>1、ACTION_DOWN-手指刚接触屏幕</p>
<p>2、ACTION_MOVE-手指在屏幕上移动</p>
<p>3、ACTION_UP-手机从屏幕上松开的一瞬间</p>
<p>正常情况下，一次手指触摸屏幕的行为会触发一系列点击事件，考虑如下几种情况：</p>
<p>1、点击屏幕后离开松开，事件序列为 DOWN -&gt; UP</p>
<p>2、点击屏幕滑动一会再松开，事件序列为DOWN-&gt;MOVE-&gt;…-&gt;UP</p>
<p>通过MotionEvent对象我们可以得到点击事件发生的x和y坐标，getX/getY返回的是相对于当前View左上角的x和y坐标，getRawX和getRawY是相对于手机屏幕左上角的x和y坐标。</p>
<p>TouchSlop:</p>
<p>TouchSlope是系统所能识别出的可以被认为是滑动的最小距离，获取方式是ViewConfiguration.get(getContext()).getScaledTouchSlope()。</p>
<ul>
<li>VelocityTracker、GestureDetector和Scroller</li>
</ul>
<p>1、 VelocityTracker：用于追踪手指在滑动过程中的速度，包括水平和垂直方向上的速度。</p>
<p>VelocityTracker的使用方式：</p>
<pre><code>//初始化
VelocityTracker mVelocityTracker = VelocityTracker.obtain();

//在onTouchEvent方法中
mVelocityTracker.addMovement(event);

//获取速度
mVelocityTracker.computeCurrentVelocity(1000);

float xVelocity = mVelocityTracker.getXVelocity();
//重置和回收

mVelocityTracker.clear(); //一般在MotionEvent.ACTION_UP的时候调用

mVelocityTracker.recycle(); //一般在onDetachedFromWindow中调用
</code></pre><p>速度的计算公式：</p>
<p>速度 = （终点位置 - 起点位置） / 时间段</p>
<p>速度可能为负值，例如当手指从屏幕右边往左边滑动的时候。此外，速度是单位时间内移动的像素数，单位时间不一定是1秒钟，可以使用方法computeCurrentVelocity(xxx)指定单位时间是多少，单位是ms。例如通过computeCurrentVelocity(1000)来获取速度，手指在1s中滑动了100个像素，那么速度是100，即100(像素/1000ms)。如果computeCurrentVelocity(100)来获取速度，在100ms内手指只是滑动了10个像素，那么速度是10，即10(像素/100ms)。</p>
<p>当不需要的时候，需要调用clear方法来重置并回收内存</p>
<pre><code>velocityTracker.clear();
velocityTracker.recycler();
</code></pre><p>​    </p>
<p>2、GestureDetector</p>
<p>手势检测，用于辅助检测用户的点击、滑动、长按、双击等行为。</p>
<p>在日常开发中，比较常用的有:onSingleTapUp(单击)、onFling(快速滑动)、onScroll（拖动）、onLongPress(长按)、onDoubleTap(双击)，建议：如果只是监听滑动相关的事件在onTouchEvent中实现；如果要监听双击这种行为的话，那么就使用GestureDetector。</p>
<p>3、Scroller</p>
<p>弹性滑动对象，用于实现View的弹性滑动。Scroller本身无法让View弹性滑动，它需要和View的computeScroll方法配合使用才能共同完成这个功能。</p>
<p>##View的滑动</p>
<p>通过三种方式可以实现View的滑动</p>
<ul>
<li>第一种是通过View本身提供的scrollTo/scrollBy方法来实现滑动</li>
<li>第二种是通过动画给View施加平移效果来实现滑动</li>
<li>通过改变View的LayoutParams使得View重新布局从而实现滑动</li>
</ul>
<p>1、使用scrollTo/scrollBy<br>scrollTo和scrollBy方法只能改变view内容的位置而不能改变view在布局中的位置。 scrollBy是基于当前位置的相对滑动，而scrollTo是基于所传参数的绝对滑动。通过View的getScrollX和getScrollY方法可以得到滑动的距离。</p>
<p>2、使用动画<br>使用动画来移动view主要是操作view的translationX和translationY属性，既可以使用传统的view动画，也可以使用属性动画，使用后者需要考虑兼容性问题，如果要兼容Android3.0一下版本系统的话推荐使用nineoldandroids。使用动画还存在一个交互问题：在android3.0以前的系统上，view动画和属性动画，新位置均无法触发点击事件，同时，老位置仍然可以触发单击事件。从3.0开始，属性动画的单击事件触发位置为移动后的位置，view动画仍然在原位置。</p>
<p>3、改变布局参数<br>通过改变LayoutParams的方式去实现View的滑动是一种灵活的方法。</p>
<p>4、各种滑动方式的对比</p>
<ul>
<li>scrollTo/scrollBy:操作简单，适合对View内容的滑动</li>
<li>动画：操作简单，主要适用于没有交互的View和实现复杂的动画效果</li>
<li>改变布局参数：操作稍微复杂，适用于有交互的View</li>
</ul>
<p>动画兼容库nineoldandroids中的ViewHelper类提供了很多的get/set方法来为属性动画服务，例如setTranslationX和setTranslationY方法，这些方法是没有版本要求的。</p>
<p>##弹性滑动</p>
<p>1、使用Scroller<br>Scroller的工作原理：Scroller本身并不能实现view的滑动，它需要配合view的computeScroll方法才能完成弹性滑动的效果，它不断地让view重绘，而每一次重绘距滑动起始时间会有一个时间间隔，通过这个时间间隔Scroller就可以得出view的当前的滑动位置，知道了滑动位置就可以通过scrollTo方法来完成view的滑动。就这样，view的每一次重绘都会导致view进行小幅度的滑动，而多次的小幅度滑动就组成了弹性滑动，这就是Scroller的工作原理。</p>
<p>2、通过动画<br>采用这种方法除了能完成弹性滑动以外，还可以实现其他动画效果，我们完全可以在onAnimationUpdate方法中加上我们想要的其他操作。</p>
<p>3、使用延时策略<br>使用延时策略来实现弹性滑动，它的核心思想是通过发送一系列延时消息从而达到一种渐进式的效果，具体来说可以使用Handler的sendEmptyMessageDelayed(xxx)或view的postDelayed方法，也可以使用线程的sleep方法。</p>
<p>##View的事件分发机制</p>
<p>1、事件分发机制的三个重要方法</p>
<ul>
<li>public boolean dispatchTouchEvent(MotionEvent ev)</li>
</ul>
<p>用来进行事件的分发。如果事件能够传递给当前的View，那么此方法一定会被调用，返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响，表示是否消耗当前事件。</p>
<ul>
<li>public boolean onInterceptTouchEvent(MotionEvent event)</li>
</ul>
<p>在上述方法内部调用，用来判断是否拦截某个事件，如果当前View拦截了某个事件，那么在同一个事件序列当中，此方法不会被再次调用，返回结果表示是否拦截当前事件。</p>
<ul>
<li>public boolean onTouchEvent(MotionEvent event)</li>
</ul>
<p>在dispatchTouchEvent方法中调用，用来处理点击事件，返回结果表示是否消耗当前的事件，如果不消耗，则在同一个事件序列中，当前View无法再次接受到事件。</p>
<p>这三个方法的关系可以用如下伪代码表示：</p>
<pre><code>public boolean dispatchTouchEvent(MotionEvent event)
{
    boolean consume = false;
    if(onInterceptTouchEvent(ev))
    {
        consume = onTouchEvent(ev);
    }
    else
    {
        consume = child.dispatchTouchEvent(ev);
    }
}
</code></pre><p>我们可以大致了解点击事件的传递规则：对于一个根ViewGroup来说，点击事件产生后，首先会传递给它，这时它的dispatchTouchEvent会被调用，如果这个ViewGroup的onInterceptTouchEvent方法返回true，就表示它要拦截当前事件，接着事件就会交给这个ViewGroup处理，即它的onTouchEvent方法就会被调用；如果这个ViewGroup的onInterceptTouchEvent方法返回false，就表示它不拦截当前事件，这时当前事件就会继续传递给它的子元素，接着子元素的dispatchTouchEvent方法就会被调用，如此反复直到事件被最终处理。</p>
<p>####OnTouchListener的优先级比onTouchEvent要高</p>
<p>如果给一个view设置了OnTouchListener，那么OnTouchListener中的onTouch方法会被回调。这时事件如何处理还要看onTouch的返回值，如果onTouch返回false，那么当前view的onTouchEvent方法会被调用；如果onTouch返回true，那么onTouchEvent方法将不会被调用。</p>
<p>在onTouchEvent方法中，如果当前view设置了OnClickListener，那么它的onClick方法会被调用，所以OnClickListener的优先级最低。</p>
<p>当点击一个事件产生后，它的传递过程遵循如顺序，Activity-&gt;Window-&gt;View。如果一个View的onTouchEvent方法返回false，那么它的父容器的onTouchEvent方法将会被调用，依次类推，如果所有的元素都不处理这个事件，那么这个事件将会最终传递给Activity处理（调用Activity的onTouchEvent方法）</p>
<p>关于事件传递的机制，给出一些结论：</p>
<ul>
<li>同一个事件序列是以down事件开始，中间含有数量不定的move事件，最终以up事件结束。</li>
<li>正常情况下，一个事件序列只能被一个View拦截且消耗。一旦一个元素拦截了某次事件，那么同一个事件序列内的所有事件都会直接交给它处理，因此同一个事件序列中的事件不能分别由两个View同时处理，但是通过特殊手段可以做到，比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。</li>
<li>某个View一旦开始处理事件，如果它不消耗ACTION_DOWN事件，那么同一事件序列的其他事情都不会再交给它来处理，并且事件将重新交给它的父容器去处理（调用父容器的onTouchEvent方法）；如果它消耗ACTION_DOWN事件，但是不消耗其他类型事件，那么这个点击事件会消失，<strong>父容器的onTouchEvent方法不会被调用</strong>，当前view依然可以收到后续的事件，但是这些事件最后都会传递给<strong>Activity处理</strong>。</li>
<li>ViewGroup默认不拦截任何事件。Android源码中<strong>ViewGroup的onInterceptTouchEvent方法默认返回false，View没有onInterceptTouchEvent方法</strong>，一旦有点击事件传递给它，那么它的onTouchEvent方法就会调用。</li>
<li>View的<strong>onTouchEvent默认都会消耗事件（返回true），除非它是不可点击的（clickable和longClickable同时为false）</strong>。View的longClickable属性默认都为false，clickable要分情况，比如Button的clickable属性默认为true，而TextView的clickable属性默认为false。</li>
<li><strong>View的enable属性不影响onTouchEvent的默认返回值</strong>，哪怕一个View是disable状态的，只要它的clickable或者longClickable有一个为true，那么它的onTouchEvent就返回true。</li>
<li>事件传递过程总是先传递给父元素，然后再由父元素分发给子view，通过<strong>requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程，但是ACTION_DOWN事件除外</strong>，即当面对ACTION_DOWN事件时，ViewGroup总是会调用自己的onInterceptTouchEvent方法来询问自己是否要拦截事件。</li>
</ul>
<p>##View的滑动冲突</p>
<p>1、常见的滑动冲突场景</p>
<ul>
<li>外部滑动方向与内部滑动方向不一致，比如ViewPager中包含ListView</li>
<li>外部滑动方向与内部滑动方向一致</li>
<li>上面两种情况的嵌套</li>
</ul>
<p>2、滑动冲突的处理规则</p>
<p>可以根据滑动距离和水平方向形成的夹角；或者根绝水平和竖直方向滑动的距离差；或者两个方向上的速度差等。</p>
<p>3、滑动冲突的解决方式</p>
<ul>
<li>外部拦截法</li>
</ul>
<p>点击事件都经过父容器的拦截处理，如果父容器需要此事件就拦截，如果不需要此事件就不拦截，该方法需要重写父容器的onInterceptTouchEvent方法，再内部做相应的拦截即可，伪代码如下：</p>
<blockquote>
<ul>
<li>首先，ACTION_DOWN这个事件，父容器必须返回false,即不拦截ACTION_DOWN事件，因为一旦父容器拦截了ACTION_DOWN,那么后续的ACTION_MOVE/ACTION_UP都会直接交给父容器处理；</li>
<li>其次，ACTION_MOVE,根据需求来决定是否要拦截;</li>
<li>最后，ACTION_UP事件,这里必须要返回false,在这里没有多大意义</li>
</ul>
</blockquote>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">public boolean onInterceptTouchEvent(MotionEvent event) &#123;</div><div class="line">	boolean intercepted = false;</div><div class="line">	int x = (int) event.getX();</div><div class="line">  		int y = (int) event.getY();</div><div class="line"></div><div class="line">   	switch (event.getAction()) &#123;</div><div class="line">   	case MotionEvent.ACTION_DOWN: &#123;</div><div class="line">   	    intercepted = false;</div><div class="line">   	    break;</div><div class="line">   	&#125;</div><div class="line">   	case MotionEvent.ACTION_MOVE: &#123;</div><div class="line">    	   int deltaX = x - mLastXIntercept;</div><div class="line">    	   int deltaY = y - mLastYIntercept;</div><div class="line">    	   if (父容器需要拦截当前点击事件的条件，例如：Math.abs(deltaX) &gt; Math.abs(deltaY)) &#123;</div><div class="line">      	     intercepted = true;</div><div class="line">      	 &#125; else &#123;</div><div class="line">       	    intercepted = false;</div><div class="line">     	  &#125;</div><div class="line">      	 break;</div><div class="line">   	&#125;</div><div class="line">   	case MotionEvent.ACTION_UP: &#123;</div><div class="line">    	   intercepted = false;</div><div class="line">       	break;</div><div class="line">   	&#125;	</div><div class="line">   	default:</div><div class="line">       	break;</div><div class="line">   	&#125;</div><div class="line">   </div><div class="line">   	mLastXIntercept = x;</div><div class="line">   	mLastYIntercept = y;</div><div class="line">   </div><div class="line">   	return intercepted;</div><div class="line">   &#125;</div></pre></td></tr></table></figure>
<ul>
<li>内部拦截法</li>
</ul>
<p>父容器不拦截任何事件，所有的事件都传递给子元素，如果子元素需要此事件就直接消耗掉，否则就由父容器进行处理，这种方法和Android中的事件分发机制不一样，需要配合requestDisallowInterceptTouchEvent方法才能正常工作。</p>
<blockquote>
<ul>
<li>父元素需要默认拦截除ACTION_DOWN以外的事件,这样子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时，父元素才能继续拦截需要的事件。</li>
<li>ACTION_DOWN事件不受requestDisallowInterceptTouchEvent方法影响,所以一旦父元素拦截ACTION_DOWN事件,那么所有元素都无法传递到子元素去。</li>
</ul>
</blockquote>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div></pre></td><td class="code"><pre><div class="line">public boolean dispatchTouchEvent(MotionEvent event) &#123;</div><div class="line">	int x = (int) event.getX();</div><div class="line">	int y = (int) event.getY();</div><div class="line"></div><div class="line">	switch (event.getAction()) &#123;</div><div class="line">	case MotionEvent.ACTION_DOWN: &#123;</div><div class="line">	    getParent().requestDisallowInterceptTouchEvent(true);</div><div class="line">    	break;</div><div class="line">	&#125;</div><div class="line">	case MotionEvent.ACTION_MOVE: &#123;</div><div class="line">    	int deltaX = x - mLastX;</div><div class="line">    	int deltaY = y - mLastY;</div><div class="line">    	if (当前view需要拦截当前点击事件的条件，例如：	Math.abs(deltaX) &gt; Math.abs(deltaY)) 				&#123;</div><div class="line">        		getParent().requestDisallowInterceptTouchEvent(false);</div><div class="line">    	&#125;</div><div class="line">    	break;</div><div class="line">	&#125;</div><div class="line">	case MotionEvent.ACTION_UP: &#123;</div><div class="line">    	break;</div><div class="line">	&#125;</div><div class="line">	default:</div><div class="line">    	break;</div><div class="line">	&#125;</div><div class="line"></div><div class="line">	mLastX = x;</div><div class="line">	mLastY = y;</div><div class="line">	return super.dispatchTouchEvent(event);</div><div class="line">&#125;</div></pre></td></tr></table></figure>

        
        </div>
        <footer class="article-footer">
            <div class="share-container">



</div>

    <a data-url="http://yoursite.com/2017/03/15/android/books/《Android开发艺术探索》Chap3_View的事件体系/" data-id="cj6oqpm0w0013yjpjrfb8t817" class="article-share-link"><i class="fa fa-share"></i>Share</a>
<script>
    (function ($) {
        // Prevent duplicate binding
        if (typeof(__SHARE_BUTTON_BINDED__) === 'undefined' || !__SHARE_BUTTON_BINDED__) {
            __SHARE_BUTTON_BINDED__ = true;
        } else {
            return;
        }
        $('body').on('click', function() {
            $('.article-share-box.on').removeClass('on');
        }).on('click', '.article-share-link', function(e) {
            e.stopPropagation();

            var $this = $(this),
                url = $this.attr('data-url'),
                encodedUrl = encodeURIComponent(url),
                id = 'article-share-box-' + $this.attr('data-id'),
                offset = $this.offset(),
                box;

            if ($('#' + id).length) {
                box = $('#' + id);

                if (box.hasClass('on')){
                    box.removeClass('on');
                    return;
                }
            } else {
                var html = [
                    '<div id="' + id + '" class="article-share-box">',
                        '<input class="article-share-input" value="' + url + '">',
                        '<div class="article-share-links">',
                            '<a href="https://twitter.com/intent/tweet?url=' + encodedUrl + '" class="fa fa-twitter article-share-twitter" target="_blank" title="Twitter"></a>',
                            '<a href="https://www.facebook.com/sharer.php?u=' + encodedUrl + '" class="fa fa-facebook article-share-facebook" target="_blank" title="Facebook"></a>',
                            '<a href="http://pinterest.com/pin/create/button/?url=' + encodedUrl + '" class="fa fa-pinterest article-share-pinterest" target="_blank" title="Pinterest"></a>',
                            '<a href="https://plus.google.com/share?url=' + encodedUrl + '" class="fa fa-google article-share-google" target="_blank" title="Google+"></a>',
                        '</div>',
                    '</div>'
                ].join('');

              box = $(html);

              $('body').append(box);
            }

            $('.article-share-box.on').hide();

            box.css({
                top: offset.top + 25,
                left: offset.left
            }).addClass('on');

        }).on('click', '.article-share-box', function (e) {
            e.stopPropagation();
        }).on('click', '.article-share-box-input', function () {
            $(this).select();
        }).on('click', '.article-share-box-link', function (e) {
            e.preventDefault();
            e.stopPropagation();

            window.open(this.href, 'article-share-box-window-' + Date.now(), 'width=500,height=450');
        });
    })(jQuery);
</script>

            
    
        <a href="http://yoursite.com/2017/03/15/android/books/《Android开发艺术探索》Chap3_View的事件体系/#comments" class="article-comment-link disqus-comment-count" data-disqus-url="http://yoursite.com/2017/03/15/android/books/《Android开发艺术探索》Chap3_View的事件体系/">Comments</a>
    

        </footer>
    </div>
    
</article>



    <article id="post-learn/如何高效学习" class="article article-type-post" itemscope itemprop="blogPost">
    <div class="article-inner">
        
        
            <header class="article-header">
                
    
        <h1 itemprop="name">
            <a class="article-title" href="/2017/03/11/learn/如何高效学习/">《如何高效学习》</a>
        </h1>
    

                
                    <div class="article-meta">
                        
    <div class="article-date">
        <i class="fa fa-calendar"></i>
        <a href="/2017/03/11/learn/如何高效学习/">
            <time datetime="2017-03-11T06:21:11.000Z" itemprop="datePublished">2017-03-11</time>
        </a>
    </div>


                        
    <div class="article-category">
    	<i class="fa fa-folder"></i>
        <a class="article-category-link" href="/categories/learn/">learn</a><i class="fa fa-angle-right"></i><a class="article-category-link" href="/categories/learn/books/">books</a><i class="fa fa-angle-right"></i><a class="article-category-link" href="/categories/learn/books/《如何高效学习》/">《如何高效学习》</a>
    </div>

                        
    <div class="article-tag">
        <i class="fa fa-tag"></i>
        <a class="tag-link" href="/tags/books/">books</a>, <a class="tag-link" href="/tags/learn/">learn</a>
    </div>

                    </div>
                
            </header>
        
        
        <div class="article-entry" itemprop="articleBody">
        
            
            <h1 id="Part-1-整体性学习策略"><a href="#Part-1-整体性学习策略" class="headerlink" title="Part 1 整体性学习策略"></a>Part 1 整体性学习策略</h1><h2 id="1-1-什么是整体性学习"><a href="#1-1-什么是整体性学习" class="headerlink" title="1.1 什么是整体性学习"></a>1.1 什么是整体性学习</h2><ul>
<li>整体性学习需要采取多种途径综合学习，而不是试图在大脑中复制一个完美的拷贝。</li>
<li>整体性学习是运用你大脑里已有的丰富的神经元网络吸收、整合信息。</li>
<li>整体性学习在于创造<strong>信息的网络</strong>，一个知识与另一个知识相互关联，那些相互关联的知识网络使你真正做到对知识的完全理解，从而轻松地驾驭知识。</li>
</ul>
<h2 id="1-2-整体性学习基于三种主要观点"><a href="#1-2-整体性学习基于三种主要观点" class="headerlink" title="1.2 整体性学习基于三种主要观点"></a>1.2 整体性学习基于三种主要观点</h2><ol>
<li>结构</li>
<li>模型</li>
<li>高速公路</li>
</ol>
<h3 id="1-21-结构"><a href="#1-21-结构" class="headerlink" title="1.21 结构"></a>1.21 结构</h3><p>结构就是说一系列紧密联系的知识体系。</p>
<p>打个比方：<br>结构就好像你思想中的一座城市，在城市中有很多建筑物，建筑物之间有道路相连，有些建筑高大而且重要，与城市中的其他建筑有上百条路相连。</p>
<p>理解就是结构<strong>高度发达完善</strong>的结果。</p>
<ul>
<li>是不是有些学科你可以轻松“拿下”？你读起这些科目的书毫不费力、兴趣盎然，一切内容看起来那么浅显、通俗，原因何在？因为你的脑袋里已经有这些科目的发达知识结构。假如说结构就是脑袋里的城市的话，那么这些城市一定是道路四通八达、路面整洁而且交通井然有序，虽然车水马龙，却从不见交通堵塞。</li>
<li>相反，为什么有些学科理解起来那么困难呢？这说明城市简陋，道路不畅，甚至乱建一气，违章建筑、豆腐渣工程比比皆是，有些道路甚至无所指向。</li>
</ul>
<p>建立良好的知识结构就是绘制一份优秀的地图，就是建造一座设计良好的城市。所以建立知识结构时，你的目标就是<strong>在各种知识之间建立尽可能多的联系</strong>，聪明的人会很自然地这么做，每学习一个新概念，都会自动地与其他知识相联系。</p>
<h3 id="1-22-模型"><a href="#1-22-模型" class="headerlink" title="1.22 模型"></a>1.22 模型</h3><p>模型是简化的结构，是结构的快照，更为简单和更易储存。</p>
<p>通过将一些<strong>核心概念</strong>联系在一起，就可以创建一个模型。</p>
<p>打个比方：<br>书的目录。</p>
<p>模型是一种压缩形式，它把多个关键思想压至一处。</p>
<p>模型就像是结构的种子，是一座建筑的地基和框架，是知识最核心的概念，在此基础上将引出全部的知识。</p>
<h3 id="1-23-高速公路"><a href="#1-23-高速公路" class="headerlink" title="1.23 高速公路"></a>1.23 高速公路</h3><p>高速公路是知识体系(结构)间的联系。</p>
<p>打个比方：<br>城市之间的高速公路。</p>
<p>高速公路能激发创造力。“跳出盒子”之外思考最恰当地描述了那些在结构之外思考的人们。高速公路让他们以前所未有的方式思考，以不曾想过的方式将常人眼里风马牛不相及的专业联系在一起思考，这就是创造。利用高速公路可以在专业交叉的领域建立起新的结构。</p>
<h2 id="1-3-整体性学习的顺序"><a href="#1-3-整体性学习的顺序" class="headerlink" title="1.3 整体性学习的顺序"></a>1.3 整体性学习的顺序</h2><ol>
<li>获取</li>
<li>理解</li>
<li>拓展</li>
<li>纠错</li>
<li>应用</li>
</ol>
<h3 id="1-31-获取"><a href="#1-31-获取" class="headerlink" title="1.31 获取"></a>1.31 获取</h3><p>获取指信息从采集进入到大脑的过程。</p>
<p>获取信息有三个主要目标</p>
<ol>
<li>简化：将无用的东西尽量删除，只有那些对于形成模型有帮助的例子才需要认真学习。</li>
<li>容量：尽可能获得更多的信息。</li>
<li>速度：尽可能提高获取信息的速度。</li>
</ol>
<h3 id="1-32-理解"><a href="#1-32-理解" class="headerlink" title="1.32 理解"></a>1.32 理解</h3><p>理解不能指停留在信息的表面意思，还要了解信息(公式)是如何得来的，与其它信息(公式)的关系。</p>
<h3 id="1-33-拓展"><a href="#1-33-拓展" class="headerlink" title="1.33 拓展"></a>1.33 拓展</h3><p>拓展：利用模型来建华和扩展你的知识结构，也要拓展对知识的理解。</p>
<p>一名整体性学习者在学习新公式时会问道：</p>
<ul>
<li>公式是怎么来的？</li>
<li>公式中的每个成分代表的真实含义是什么？</li>
<li>公式中的什么成分可以做些改变？</li>
<li>这种改变会导致结果发生什么样的变化？</li>
<li>其他公式与这个公式有什么相同之处，又有什么不同之处？</li>
</ul>
<p>拓展有三种主要方式：</p>
<ol>
<li>深度拓展(知识背景探究)：在信息内部创造联系。</li>
<li>横向拓展(类似知识探究)：与此类似的结论有哪些？那些地方类似？那些地方不同？</li>
<li>纵向拓展(创造性探究)：在结构间建立高速通道，联系不同知识体系。</li>
</ol>
<h3 id="1-34-纠错"><a href="#1-34-纠错" class="headerlink" title="1.34 纠错"></a>1.34 纠错</h3><p>纠错可以看作在整体性知识网络中做修剪工作，添加一些特殊的例子，删除一些现实中不存在的联系，这不是一个完美的过程，修剪工作是必要的。</p>
<h3 id="1-35-应用阶段"><a href="#1-35-应用阶段" class="headerlink" title="1.35 应用阶段"></a>1.35 应用阶段</h3><p>应用是学习的最终目的，能做到学以致用方能更好地适应现实世界。</p>
<p>对概念不仅仅停留在理解上，而是要<strong>形成一种感觉</strong>。</p>
<h3 id="1-36-测试阶段"><a href="#1-36-测试阶段" class="headerlink" title="1.36 测试阶段"></a>1.36 测试阶段</h3><p>五个阶段都要进行<a href="http://lib.csdn.net/base/softwaretest" target="_blank" rel="external">测试</a>，测试可以让你了解弱点是什么，该如何改进它。</p>
<ol>
<li>获取阶段的测试──我以前看过或听过这个知识吗？</li>
<li>理解阶段的测试──我理解知识的含义吗？</li>
<li>拓展阶段的测试──我知道知识从何而来，与哪些知识有关系吗？</li>
<li>纠错阶段的测试──我删除了那些不恰当的联系吗？我删除了那些错误结论吗？</li>
<li>应用阶段的测试──我将知识用到实际生活中了吗？</li>
</ol>
<h2 id="1-4-找出薄弱环节"><a href="#1-4-找出薄弱环节" class="headerlink" title="1.4 找出薄弱环节"></a>1.4 找出薄弱环节</h2><ol>
<li>获取阶段──养成良好的学习，阅读和记笔记的习惯。</li>
<li>理解阶段──放慢阅读速度，寻找不同的说法。</li>
<li>拓展阶段──提高灵活性，做到举一反三，触类旁通。</li>
<li>纠错阶段──对建立的联系做足够严谨的检查。</li>
<li>应用阶段──抛开书本，做实验，接触生活。</li>
</ol>
<h2 id="1-5-信息结构"><a href="#1-5-信息结构" class="headerlink" title="1.5 信息结构"></a>1.5 信息结构</h2><ol>
<li>随意信息：缺少逻辑分类多零散知识。</li>
<li>观点信息：存在争论的信息。</li>
<li>过程信息：讲述一系列动作，操作的信息。</li>
<li>具体信息：实际生活中可以观察到，听到，触到的信息。</li>
<li>抽象信息：难以理解，但是逻辑性很强，需要抽象思维理解的信息。</li>
</ol>
<h1 id="Part-2-整体性学习技术"><a href="#Part-2-整体性学习技术" class="headerlink" title="Part 2 整体性学习技术"></a>Part 2 整体性学习技术</h1><h2 id="2-1-获取知识"><a href="#2-1-获取知识" class="headerlink" title="2.1 获取知识"></a>2.1 获取知识</h2><ol>
<li>快速阅读</li>
<li>笔记流</li>
</ol>
<h3 id="2-11-快速阅读"><a href="#2-11-快速阅读" class="headerlink" title="2.11 快速阅读"></a>2.11 快速阅读</h3><ol>
<li>指读法:用手指帮助阅读</li>
<li>练习阅读法:两次在相同时间里阅读相同的资料，比较理解程度</li>
<li>积极阅读法(针对难以理解的知识)<br>这一节主要观点是什么我怎样才能记住主要观点我要怎样将主要观点拓展开以及应用它</li>
</ol>
<h3 id="2-12-笔记流"><a href="#2-12-笔记流" class="headerlink" title="2.12 笔记流"></a>2.12 笔记流</h3><p>笔记流的制作：</p>
<ol>
<li>使用笔记流时，首先写下最主要的观点，尽量用很短的单词来替换完整的句子。</li>
<li>一旦你写下了一个观点，下一步就是在这个观点和其他观点之间画上一些箭头，观点不是建立成金字塔式的结构，而是呈现出相互关联的关系。</li>
</ol>
<p>两种笔记流：</p>
<ul>
<li>课下笔记流：课堂上按常规记笔记，下课后根据笔记内容制作标准的笔记流。</li>
<li>评注流：首先写下最关键的信息，然后在信息之间加入联系</li>
</ul>
<h2 id="2-2-联系观点"><a href="#2-2-联系观点" class="headerlink" title="2.2 联系观点"></a>2.2 联系观点</h2><p>在获得知识之后，仅仅理解知识的表面意思一般是记不牢的，我们需要利用技巧理解和拓展这些知识。</p>
<h3 id="2-21-比喻"><a href="#2-21-比喻" class="headerlink" title="2.21 比喻"></a>2.21 比喻</h3><p>比喻就是在不熟悉的知识和熟悉的知识之间架起一座沟通的桥梁。</p>
<p>找出比喻的步骤：</p>
<ol>
<li>确定你要深入理解和记忆的信息。</li>
<li>在你的个人经验中寻找与信息部分相似的东西，要达到完全符合不太可能，所以与其寻找一个完全符合的东西，不如稍作让步，找到十几个部分符合的“不完美比喻”，在上面的例子里我想到的是雪中行走。</li>
<li>重复上述过程，检查比喻不恰当的地方。例如，雪中行走是线性的痕迹，而脑神经却是错综复杂的网络。</li>
</ol>
<p>运用比喻法的技巧</p>
<ul>
<li>要有寻找比喻的欲望</li>
<li>注意第一个出现在脑海中的念头</li>
<li>优化和测试你的比喻(多找几个比喻)</li>
</ul>
<h3 id="2-22-内在化"><a href="#2-22-内在化" class="headerlink" title="2.22 内在化"></a>2.22 内在化</h3><p>内在化：在脑海中出现图像，而且有声音，触觉和情感</p>
<p>注意：具体信息的内在化效果最好，而抽象信息最适合比喻法</p>
<p>怎样进行内在化</p>
<ol>
<li>明确你要内在化的概念。这是一个生物过程，还是编程中的函数或者是一个数学概念？</li>
<li>从建立脑海中的图像开始。如果你不习惯内在化，可以先试着在纸上画出概念的粗略图，多次尝试后，你就会直接在脑海中想象了。</li>
<li>脑海中的图像是静态的，还是栩栩如生的动态场景？掌握一个行列式需要好几个步骤，所以要让图像动起来，就仿佛在看一部电影一样。</li>
<li>现在开始加上其他感官。试着用手去拿它，去摸它，去打开它，去嗅它的味道，去听它的声音，动用你身体的所有感官，将所有的感觉与运动的图像相联系。</li>
<li>加入更多的感觉或情感。</li>
<li>不断重复和优化图像，直到你一想到它就能很快地回忆起知识。</li>
</ol>
<h3 id="2-23-图表法"><a href="#2-23-图表法" class="headerlink" title="2.23 图表法"></a>2.23 图表法</h3><ol>
<li>流程图：一系列的步骤；历史事件；一个系统。</li>
<li>概念图：观点之间的内在关系</li>
<li>图像：用粗糙简单的涂鸦来代替文字，包括观点和观点之间的联系。</li>
</ol>
<h2 id="2-3-随意信息的处理"><a href="#2-3-随意信息的处理" class="headerlink" title="2.3 随意信息的处理"></a>2.3 随意信息的处理</h2><ol>
<li>联想法</li>
<li>挂钩法</li>
<li>信息压缩技术</li>
</ol>
<h3 id="2-31-联想法"><a href="#2-31-联想法" class="headerlink" title="2.31 联想法"></a>2.31 联想法</h3><p>联想法：将一系列观点串在一起，就像链条。</p>
<p>联想法的步骤： </p>
<ol>
<li>创造顺序 </li>
<li>给每一项预设一个符号 </li>
<li>创建属于自己的联想，前后项的联系</li>
</ol>
<h3 id="2-32-挂钩法"><a href="#2-32-挂钩法" class="headerlink" title="2.32 挂钩法"></a>2.32 挂钩法</h3><p>挂钩法：将要记忆的信息与数字联系在一起。</p>
<h3 id="2-33-信息压缩技术"><a href="#2-33-信息压缩技术" class="headerlink" title="2.33 信息压缩技术"></a>2.33 信息压缩技术</h3><p>信息压缩：减少信息的容量，寻找信息的逻辑关系</p>
<p>信息压缩的三种方式：</p>
<ul>
<li>记忆术:用一个短语储存数个信息的方法<ol>
<li>图像联系：简单的符号替代每一个信息，然后将这些符号放在一张图像中</li>
<li>笔记压缩:</li>
<li>拿几张空白纸。准备好要压缩的笔记，这个练习可能会花费一两小时，所以要确保你有这么多时间。<ul>
<li>用最小的字，写下笔记中的主要观点。使用尽可能少的字。</li>
<li>接着写下与之相联系的观点、公式、概念或定义。尽可能写小一点和写少一点，节省空间。</li>
<li>持续上述自由写下观点的过程，直到将笔记中的每一个主要观点都写了下来为止。最后你得到大约1～3张密密麻麻写满信息的纸。</li>
<li>有时候，还可以更进一步，将上面压缩后的内容加工、修饰得更有条理，更好看一些。</li>
</ul>
</li>
</ol>
</li>
</ul>
<h2 id="2-4-以项目为基础的学习"><a href="#2-4-以项目为基础的学习" class="headerlink" title="2.4 以项目为基础的学习"></a>2.4 以项目为基础的学习</h2><p>以项目为基础的学习方式简单地说就是设定一个需要1～3个月达成的目标，达成目标的过程就是学习的过程，达成了目标也就达到了学习的效果。为了完成项目，你必须围绕着目标努力学习，以项目为基础的学习符合整体性学习的每一个过程。</p>
<h2 id="2-5-整体性学习技术的实际应用-费曼技巧"><a href="#2-5-整体性学习技术的实际应用-费曼技巧" class="headerlink" title="2.5 整体性学习技术的实际应用-费曼技巧"></a>2.5 整体性学习技术的实际应用-费曼技巧</h2><p>费曼技巧：</p>
<p>第一步：选择要学习的概念<br>首先选好你打算深入理解的概念，拿一张空白纸，在最上方写下概念的名称。</p>
<p>第二步：设想你是老师，正在试图教会一名新生这个知识点<br>这一步你要假想自己费尽口舌让一名毫无这方面知识的学生听懂，并把你的解释记录下来。这一步至关重要，因为在自我解释那些你理解或不理解的知识过程中，你会理解得更好，而原先不明白的地方也得以理清。</p>
<p>第三步：当你感到疑惑时，返回去吧。<br>每当你碰到难题感到疑惑时，别急着往下走，学习不是单行道，回过头来，重新阅读参考材料、听讲座或找老师解答，直到你觉得搞懂了为止，然后把解释记到纸上。</p>
<p>第四步：简单化和比喻<br>如果你的解释很啰唆或者艰涩，尽量用简单直白的语言重新表述它，或者找到一个恰当的比喻以更好地理解它。</p>
<p>你可以通过这种技术仔细地查明到底是什么地方你不明白，然后你可以去翻阅教材、笔记或询问老师，弄清楚到底你遗漏了哪些关键知识。</p>
<p>费曼技巧对于自我测试、考察对知识点的理解程度，是一个真正的好方法。因为如果你不翻阅教科书就能用自己的话把观点解释清楚，那么就意味着你真正理解了该观点。</p>
<h1 id="Part-3-超越整体性学习"><a href="#Part-3-超越整体性学习" class="headerlink" title="Part 3 超越整体性学习"></a>Part 3 超越整体性学习</h1><h2 id="3-1-成为高效学生的关键点："><a href="#3-1-成为高效学生的关键点：" class="headerlink" title="3.1 成为高效学生的关键点："></a>3.1 成为高效学生的关键点：</h2><ol>
<li>能量管理</li>
<li>不要“学习”（Don’t study）</li>
<li>绝不拖延时间</li>
<li>批处理</li>
<li>有组织</li>
</ol>
<h3 id="3-11-能量管理"><a href="#3-11-能量管理" class="headerlink" title="3.11 能量管理"></a>3.11 能量管理</h3><ul>
<li>增加你的能量储备<ul>
<li>坚持运动</li>
<li>充足的睡眠</li>
<li>多吃粗纤维，粗加工的食物</li>
<li>多喝水</li>
<li>少吃多餐</li>
</ul>
</li>
<li>将日程表由线性的改为循环式的<ul>
<li>一周休息一天</li>
<li>工作在白天集中完成</li>
<li>设定90分钟集中完成某任务</li>
</ul>
</li>
</ul>
<h3 id="3-12-不要学习"><a href="#3-12-不要学习" class="headerlink" title="3.12 不要学习"></a>3.12 不要学习</h3><p>大多数学生认为如果不能一直待在图书馆、坐在书桌前沉思苦读，就会有负罪感。于是学生生活变成了一种持久的自我斗争过程──总是试图多‘学习’，但总是感到收获少。这就好像总是做出一副吃饭的样子，但是吃下去得不多，消化得不够，吸收得也不好，如此这般学习效果怎么可能好呢？</p>
<p>不用“学习”（学习就好比整个食物经过咀嚼、消化、吸收的过程，它不是表面看起来“吃”的动作。人们不可能永远吃个不停，所以学习也不是坐在书桌前越长越好，学习需要挑选要吃的食物（获取信息）、咀嚼（明白阶段）、消化（理解阶段）、吸收（应用阶段）。 学习的定义：学习不是指坐在书桌前12个小时，也不是指每天坚持看书。这些是学习的表象，不是学习的实质。</p>
<h3 id="3-13-绝不拖延"><a href="#3-13-绝不拖延" class="headerlink" title="3.13 绝不拖延"></a>3.13 绝不拖延</h3><p>周/日目标体系:</p>
<ol>
<li>每周周末，列一个清单，包括所有的任务、作业以及你想在下周完成的读书和学习活动。</li>
<li>每天晚上，检查周计划，列出每日目标清单。</li>
</ol>
<h3 id="3-14-批处理"><a href="#3-14-批处理" class="headerlink" title="3.14 批处理"></a>3.14 批处理</h3><p>批处理的意思就是将那些类似的、散在的工作集中起来一次做完。批处理有助于节省时间，因为你可以集中时间和精力。</p>
<p>例如：某个时间把一周需要阅读的材料一次性读完，而抽出另外一个时间连续写三四篇文章。</p>
<p>批处理的使用技巧：</p>
<ul>
<li>批处理最适用于将那些需要时间不长的零散工作放在一起做。</li>
<li>一次性完成作业。如果一项作业花费的时间不会超过8小时，我就坐在凳子上一鼓作气完成它。将一个花3小时就可以写完的文章，分成15次零散时间内完成，肯定会浪费很多时间，每一次都要花时间重新鼓起写作的激情才能开始正式的写作。</li>
<li>提髙注意力阈值。注意力阈值就是指集中完成某项工作的最长时间，超过这个时间，注意力就急剧下降。通过不断接受越来越多的批处理，你可以逐渐提高你的注意力阈值。阈值越高，表示能一次完成的工作量也越大。</li>
</ul>
<h3 id="3-15-有组织"><a href="#3-15-有组织" class="headerlink" title="3.15 有组织"></a>3.15 有组织</h3><p>让你变得有组织：</p>
<ul>
<li>所有的物品都放在固定的位置。作业、课外作业以及约会日期都记录在固定的地方。没有固定地方的结果就是混乱。专门找一个地方放东西，这样会更容易放得有序。</li>
<li>随身携带一个记事本。每天随时记录，日积月累，必有所获。</li>
<li>坚持日历和做事清单。应用做事清单记录任务和目标，使用日历来记录要做的事和截止日期。</li>
</ul>

        
        </div>
        <footer class="article-footer">
            <div class="share-container">



</div>

    <a data-url="http://yoursite.com/2017/03/11/learn/如何高效学习/" data-id="cj6oqplym0003yjpjnxl9vrcq" class="article-share-link"><i class="fa fa-share"></i>Share</a>
<script>
    (function ($) {
        // Prevent duplicate binding
        if (typeof(__SHARE_BUTTON_BINDED__) === 'undefined' || !__SHARE_BUTTON_BINDED__) {
            __SHARE_BUTTON_BINDED__ = true;
        } else {
            return;
        }
        $('body').on('click', function() {
            $('.article-share-box.on').removeClass('on');
        }).on('click', '.article-share-link', function(e) {
            e.stopPropagation();

            var $this = $(this),
                url = $this.attr('data-url'),
                encodedUrl = encodeURIComponent(url),
                id = 'article-share-box-' + $this.attr('data-id'),
                offset = $this.offset(),
                box;

            if ($('#' + id).length) {
                box = $('#' + id);

                if (box.hasClass('on')){
                    box.removeClass('on');
                    return;
                }
            } else {
                var html = [
                    '<div id="' + id + '" class="article-share-box">',
                        '<input class="article-share-input" value="' + url + '">',
                        '<div class="article-share-links">',
                            '<a href="https://twitter.com/intent/tweet?url=' + encodedUrl + '" class="fa fa-twitter article-share-twitter" target="_blank" title="Twitter"></a>',
                            '<a href="https://www.facebook.com/sharer.php?u=' + encodedUrl + '" class="fa fa-facebook article-share-facebook" target="_blank" title="Facebook"></a>',
                            '<a href="http://pinterest.com/pin/create/button/?url=' + encodedUrl + '" class="fa fa-pinterest article-share-pinterest" target="_blank" title="Pinterest"></a>',
                            '<a href="https://plus.google.com/share?url=' + encodedUrl + '" class="fa fa-google article-share-google" target="_blank" title="Google+"></a>',
                        '</div>',
                    '</div>'
                ].join('');

              box = $(html);

              $('body').append(box);
            }

            $('.article-share-box.on').hide();

            box.css({
                top: offset.top + 25,
                left: offset.left
            }).addClass('on');

        }).on('click', '.article-share-box', function (e) {
            e.stopPropagation();
        }).on('click', '.article-share-box-input', function () {
            $(this).select();
        }).on('click', '.article-share-box-link', function (e) {
            e.preventDefault();
            e.stopPropagation();

            window.open(this.href, 'article-share-box-window-' + Date.now(), 'width=500,height=450');
        });
    })(jQuery);
</script>

            
    
        <a href="http://yoursite.com/2017/03/11/learn/如何高效学习/#comments" class="article-comment-link disqus-comment-count" data-disqus-url="http://yoursite.com/2017/03/11/learn/如何高效学习/">Comments</a>
    

        </footer>
    </div>
    
</article>



    <article id="post-android/books/《Android开发艺术探索》Chap2_IPC机制" class="article article-type-post" itemscope itemprop="blogPost">
    <div class="article-inner">
        
        
            <header class="article-header">
                
    
        <h1 itemprop="name">
            <a class="article-title" href="/2017/03/07/android/books/《Android开发艺术探索》Chap2_IPC机制/">《Android开发艺术探索》Chap2_IPC机制</a>
        </h1>
    

                
                    <div class="article-meta">
                        
    <div class="article-date">
        <i class="fa fa-calendar"></i>
        <a href="/2017/03/07/android/books/《Android开发艺术探索》Chap2_IPC机制/">
            <time datetime="2017-03-07T09:02:25.000Z" itemprop="datePublished">2017-03-07</time>
        </a>
    </div>


                        
    <div class="article-category">
    	<i class="fa fa-folder"></i>
        <a class="article-category-link" href="/categories/android/">android</a><i class="fa fa-angle-right"></i><a class="article-category-link" href="/categories/android/books/">books</a><i class="fa fa-angle-right"></i><a class="article-category-link" href="/categories/android/books/《Android开发艺术探索》/">《Android开发艺术探索》</a>
    </div>

                        
    <div class="article-tag">
        <i class="fa fa-tag"></i>
        <a class="tag-link" href="/tags/android/">android</a>, <a class="tag-link" href="/tags/books/">books</a>
    </div>

                    </div>
                
            </header>
        
        
        <div class="article-entry" itemprop="articleBody">
        
            
            <p>注：此篇笔记只记录重难点，对于基础和详细内容请自行学习《Android开发艺术探索》</p>
<hr>
<h2 id="2-1-IPC简介"><a href="#2-1-IPC简介" class="headerlink" title="2.1 IPC简介"></a>2.1 IPC简介</h2><p>Inter-Process Communication的缩写。含义为进程间通信或跨进程通信，是指两个进程之间进行数据交换的过程。</p>
<h4 id="进程和线程的区别"><a href="#进程和线程的区别" class="headerlink" title="进程和线程的区别"></a>进程和线程的区别</h4><ul>
<li>按照操作系统的描述，线程是CPU调度的最小单元，同时线程是一种有限的系统资源。</li>
<li>进程一般指一个执行单元，在PC和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程，因此进程和线程是包含与被包含的关系。</li>
</ul>
<h4 id="多进程分为两种"><a href="#多进程分为两种" class="headerlink" title="多进程分为两种"></a>多进程分为两种</h4><ul>
<li>第一种情况是一个应用因为某些原因自身需要采用多线程模式来实现。</li>
<li>另一种情况是当前应用需要向其他应用获取数据</li>
</ul>
<h2 id="2-2-Android中的多进程模式"><a href="#2-2-Android中的多进程模式" class="headerlink" title="2.2 Android中的多进程模式"></a>2.2 Android中的多进程模式</h2><h3 id="2-2-1-开启多进程"><a href="#2-2-1-开启多进程" class="headerlink" title="2.2.1 开启多进程"></a>2.2.1 开启多进程</h3><p>通过给四大组件指定android:process属性，我们可以开启多线程模式</p>
<ul>
<li>进程名以”:”开头的进程属于当前应用的私有进程，其他应用的组件不可以和它跑在同一进程，而进程名不以”:”开头的进程属于全局进程，其他应用通过ShareUID方式可以和它跑在同一个进程中。</li>
<li>Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据，两个应用通过ShareUID跑在同一个进程中是有要求的，需要这两个应用有相同的ShareUID并且签名相同才可以。在这种情况下，它们可以互相访问对方的私有数据，比如data目录、组件信息等，不管它们是否跑在同一个进程中。当然如果它们跑在同一个进程中，那么除了能共享data目录、组件信息，还可以共享内存数据，或者说它们看起来就像是一个应用的两个部分。</li>
</ul>
<h3 id="2-2-2-多进程模式的运行机制"><a href="#2-2-2-多进程模式的运行机制" class="headerlink" title="2.2.2 多进程模式的运行机制"></a>2.2.2 多进程模式的运行机制</h3><ol>
<li>Android为每一个应用分配了一个独立的虚拟机，或者说为每个进程都分配了一个独立的虚拟机，不同的虚拟机在不同的内存分配上有不同的地址空间，这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。</li>
<li>所有运行在不同进程中的四大组件，只要它们之间需要通过内存来共享数据，都会共享失败。</li>
</ol>
<p>一般来说，使用多进程会造成如下几个方面的问题：</p>
<ul>
<li>静态成员和单例模式完全失效</li>
<li>线程同步机制完全失效</li>
</ul>
<p>不管是锁对象还是锁全局类都无法保证线程同步，因为不同进程锁的不是同一个对象</p>
<ul>
<li>SharedPreference的可靠性下降</li>
</ul>
<p>SharedPreferences不支持两个进程同时去执行写操作，否则会导致一定几率的数据丢失，这时因为SharedPreferences底层是通过读写XML文件来实现的，并发写显然是可能出问题的，甚至并发读写都有可能发生问题</p>
<ul>
<li>Application会多次创建</li>
</ul>
<p>运行在同一个进程中的组件是属于同一个虚拟机和同一个Application的。同理，运行在不同进程中的组件是属于两个不同的虚拟机和Application的。</p>
<h2 id="IPC基础概念介绍"><a href="#IPC基础概念介绍" class="headerlink" title="IPC基础概念介绍"></a>IPC基础概念介绍</h2><h3 id="2-3-1-Serializable接口"><a href="#2-3-1-Serializable接口" class="headerlink" title="2.3.1  Serializable接口"></a>2.3.1  Serializable接口</h3><p>是Java所提供的一个序列化接口，它是一个空接口，为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化相当简单，只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">private static final long serialVersionUID = 8711368828010083044L</div></pre></td></tr></table></figure>
<p>通过Serializable方来实现对象的序列化，如下代码：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">//序列化过程</div><div class="line">User user = new User(0, &quot;jake&quot;, true);</div><div class="line">ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(&quot;cache.txt&quot;));</div><div class="line">out.writeObject(user);</div><div class="line">out.close();</div><div class="line"></div><div class="line">//反序列化过程</div><div class="line">ObjectInputStream in = new ObjectInputStream(new FileInputStream(&quot;cache.txt&quot;));</div><div class="line">User newUser = (User)in.readObject();</div><div class="line">in.close();</div></pre></td></tr></table></figure></p>
<p>原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同时才能够正常的被反序列化。<br>serialVersionUID的详细工作机制是这样的：序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中（也可能是其他的中介），当反序列化的时候系统会去检测文件中的serialVersionUID，看它是否和当前类的serialVersionUID一致，如果一致就说明序列化的类的版本和当前类的版本是相同的，这个时候可以成功反序列化，否则就说明当前类和序列化的类相比发生了某些变换。</p>
<p>给serialVersionUID制定为1L或者采用Eclipse根据当前类结构去生成的hash值，这两者并没有本质区别。</p>
<ul>
<li>静态成员变量属于类不属于对象，所以不会参与序列化过程</li>
<li>其次用transient关键字标记的成员变量不参与序列化过程</li>
</ul>
<h3 id="2-3-2-Parcelable接口"><a href="#2-3-2-Parcelable接口" class="headerlink" title="2.3.2 Parcelable接口"></a>2.3.2 Parcelable接口</h3><p>Parcelable也是一个接口，只要实现这个接口，一个类的对象就可以实现序列化并可以通过Intent的Binder传递<br>Parcelable的方法说明：</p>
<table>
<thead>
<tr>
<th>方法</th>
<th style="text-align:center">功能</th>
<th style="text-align:right">标记位</th>
</tr>
</thead>
<tbody>
<tr>
<td>createFromParcel(Parcel in)</td>
<td style="text-align:center">从序列化的对象中创建原始对象</td>
<td style="text-align:right"></td>
</tr>
<tr>
<td>newArray[int size]</td>
<td style="text-align:center">创建指定长度的原始对象数组</td>
<td style="text-align:right"></td>
</tr>
<tr>
<td>User(Parcel in)</td>
<td style="text-align:center">从序列化的对象中创建原始对象</td>
<td style="text-align:right"></td>
</tr>
<tr>
<td>write ToParcel(Parcel out, int flags)</td>
<td style="text-align:center">将当前对象写入序列化结构中，其中flags标识有两种值0或1（参见右侧标记位）。为1时标识当前对象需要作为返回值返回，不能立即释放资源，几乎所有情况都为0</td>
<td style="text-align:right">PARCELABLE_WRITE_RETURN_VALUE</td>
</tr>
<tr>
<td>describeContents</td>
<td style="text-align:center">返回当前对象的内容描述。如果含有文件描述符，返回1（参见右侧标记位），否则返回0，几乎所有的情况都返回0</td>
<td style="text-align:right">CONTENTS_FILE_DESCRIPTOR</td>
</tr>
</tbody>
</table>
<ul>
<li><p>具体如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div></pre></td><td class="code"><pre><div class="line">public class Book implements Parcelable &#123;</div><div class="line"> public static final Creator&lt;Book&gt; CREATOR = new Creator&lt;Book&gt;() &#123;</div><div class="line">   @Override</div><div class="line">   public Book createFromParcel(Parcel in) &#123;</div><div class="line">       return new Book(in);</div><div class="line">   &#125;</div><div class="line">   @Override</div><div class="line">   public Book[] newArray(int size) &#123;</div><div class="line">       return new Book[size];</div><div class="line">   &#125;</div><div class="line"> &#125;;</div><div class="line"> public int code;</div><div class="line"> public String name;</div><div class="line"> public Book(int code, String name) &#123;</div><div class="line">   this.code = code;</div><div class="line">   this.name = name;</div><div class="line"> &#125;</div><div class="line"> protected Book(Parcel in) &#123;</div><div class="line">   code = in.readInt();</div><div class="line">   name = in.readString();</div><div class="line"> &#125;</div><div class="line"> public int describeContents() &#123;</div><div class="line">   return 0;</div><div class="line"> &#125;</div><div class="line"> @Override</div><div class="line"> public void writeToParcel(Parcel dest, int flags) &#123;</div><div class="line">   dest.writeInt(code);</div><div class="line">   dest.writeString(name);</div><div class="line"> &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
</li>
<li><p>系统已经为我们提供了许多实现了Parcelable接口的类，它们都是可以直接序列化的，比如Intent、Bundle、Bitmap等，同时List和Map也可以序列化，前提是它们里面的每个元素都是可序列化的。</p>
</li>
</ul>
<h4 id="如何选取"><a href="#如何选取" class="headerlink" title="如何选取"></a>如何选取</h4><ol>
<li>Serializable是Java中的序列化接口，其使用起来简单但是开销很大，序列化和反序列化需要大量I/O操作。而Parceleble是Android中的序列化方式，因此更适合在Android平台上，缺点是麻烦，但是效率高，这是Android推荐的序列化方式，所以我们要首选Parcelable。</li>
<li>Parcelable主要用在内存序列化上，通过Parcelable将对象序列化到存储设备中或者将对象序列化之后通过网络传输，但是过程稍显复杂，因此在这两种情况下建议大家使用Serializable。</li>
</ol>
<h3 id="2-3-3-Binder"><a href="#2-3-3-Binder" class="headerlink" title="2.3.3 Binder"></a>2.3.3 Binder</h3><blockquote>
<ul>
<li>继承了IBinder接口</li>
<li>Binder是一种跨进程通信方式</li>
<li>是ServiceManager连接各种Manager（ActivityManager,WindowManager等）和相应ManagerService的桥梁</li>
<li>从Android应用层来说，Binder是客户端和服务端进行通信的媒介，当bindService的时候，服务器会返回一个包含了服务器端业务调用的Binder对象，通过这个Binder对象，客户端就可以获取服务端提供的服务或者是数据，这里的服务包含了普通服务和基于AIDL的服务</li>
</ul>
</blockquote>
<p>aidl工具根据aidl文件自动生成的java接口解析：</p>
<ol>
<li>首先，它声明了几个接口方法，同时还声明了几个整型的id用于标识这些方法，id用于标识在transact过程中客户端所请求的到底是哪个方法；</li>
<li>接着，它声明了一个内部类Stub，这个Stub就是一个Binder类，当客户端和服务端都位于同一个进程时，方法调用不会走跨进程的transact过程，而当两者位于不同进程时，方法调用需要走transact过程，这个逻辑由Stub内部的代理类Proxy来完成。</li>
</ol>
<p>所以，这个接口的核心就是它的内部类Stub和Stub内部的代理类Proxy。 下面分析其中的方法：</p>
<ul>
<li>asInterface(android.os.IBinder obj):用于将服务器端的Binder对象转化成客户端所需的AIDL接口类型的对象，这种转换过程是区分进程的，如果客户端和服务端是在同一进程中，那么这个方法返回的是服务端的Stub对象本身，否则返回的是系统封装的Stub.Proxy对象。</li>
<li>asBinder:返回当前Binder对象</li>
<li>onTransact：这个方法运行在服务端中的Binder线程池中，当客户端发起跨进程请求时，远程请求会通过系统底层封装后交由此方法来处理。<br>这个方法的原型是public Boolean onTransact(int code, Parcelable data, Parcelable reply, int flags)<br>服务端通过code可以知道客户端请求的目标方法，接着从data中取出所需的参数，然后执行目标方法，执行完毕之后，将结果写入到reply中。如果此方法返回false，说明客户端的请求失败，利用这个特性可以做权限验证(即验证是否有权限调用该服务)。</li>
<li>Proxy#[Method]：代理类中的接口方法，这些方法运行在客户端，当客户端远程调用此方法时，它的内部实现是：首先创建该方法所需要的参数，然后把方法的参数信息写入到_data中，接着调用transact方法来发起RPC请求，同时当前线程挂起；然后服务端的onTransact方法会被调用，直到RPC过程返回后，当前线程继续执行，并从_reply中取出RPC过程的返回结果，最后返回_reply中的数据。</li>
</ul>
<h6 id="首先，当客户端发起远程请求时，由于当前线程会被挂起直至服务端进程返回数据，所以如果一个远程方法是很耗时的，那么不能在UI线程发起此远程请求；"><a href="#首先，当客户端发起远程请求时，由于当前线程会被挂起直至服务端进程返回数据，所以如果一个远程方法是很耗时的，那么不能在UI线程发起此远程请求；" class="headerlink" title="首先，当客户端发起远程请求时，由于当前线程会被挂起直至服务端进程返回数据，所以如果一个远程方法是很耗时的，那么不能在UI线程发起此远程请求；"></a>首先，当客户端发起远程请求时，由于当前线程会被挂起直至服务端进程返回数据，所以如果一个远程方法是很耗时的，那么不能在UI线程发起此远程请求；</h6><h6 id="其次，由于服务端的Binder方法运行在Binder的线程池中，所以不管Binder是否耗时都应该采用同步的方式去实现，因为它已经运行在一个线程中了。"><a href="#其次，由于服务端的Binder方法运行在Binder的线程池中，所以不管Binder是否耗时都应该采用同步的方式去实现，因为它已经运行在一个线程中了。" class="headerlink" title="其次，由于服务端的Binder方法运行在Binder的线程池中，所以不管Binder是否耗时都应该采用同步的方式去实现，因为它已经运行在一个线程中了。"></a>其次，由于服务端的Binder方法运行在Binder的线程池中，所以不管Binder是否耗时都应该采用同步的方式去实现，因为它已经运行在一个线程中了。</h6><h3 id="Binder两种重要的方法-linkToDeath-和-unlinkToDeath"><a href="#Binder两种重要的方法-linkToDeath-和-unlinkToDeath" class="headerlink" title="Binder两种重要的方法 linkToDeath 和 unlinkToDeath"></a>Binder两种重要的方法 linkToDeath 和 unlinkToDeath</h3><p>   Binder运行在服务端，如果由于某种服务端异常终止了的话会导致客户端的远程调用失败、所以Binder提供了两个配对的方法linkToDeath和unlinkToDeath，通过linkToDeath方法可以给Binder设置一个死亡代理，当Binder死亡的时候客户端就会收到通知，然后就可以重新发起连接从而恢复连接了。</p>
<p>如何给Binder设置死亡代理<br>1、声明一个DeathRecipient对象、DeathRecipient是一个接口，其内部只有一个方法bindDied，实现这个方法就可以在Binder死亡的时候收到通知了。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"></div><div class="line">private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() &#123;</div><div class="line">    @Override</div><div class="line">    public void binderDied() &#123;</div><div class="line">        if (mRemoteBookManager == null) return;</div><div class="line">        mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);</div><div class="line">        mRemoteBookManager = null;</div><div class="line">        // TODO:这里重新绑定远程Service</div><div class="line">    &#125;</div><div class="line">&#125;;</div></pre></td></tr></table></figure>
<p>2、在客户端绑定远程服务成功之后，给binder设置死亡代理</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);</div></pre></td></tr></table></figure>
<h3 id="2-4-Android的IPC方式"><a href="#2-4-Android的IPC方式" class="headerlink" title="2.4 Android的IPC方式"></a>2.4 Android的IPC方式</h3><p>1、 使用Bundle</p>
<p>Bundle实现了Parcelable接口，Activity、Service和Receiver都支持在Intent中传递Bundle数据</p>
<p>2、 使用文件共享</p>
<p>这种方式简单，适合在对数据同步要求不高的进程之间进行通信，并且要妥善处理并发读写的问题，SharedPreferences是一个特例，虽然它也是文件的一种，但是由于系统对它的读写有一定的缓存策略，即在内存中会有一份SharedPreferences文件的缓存，因此在多进程模式下、系统对它的读写就变的不可靠，当面对高并发读写访问的时候，有很大几率会丢失，因此，不建议在进程间通信中使用SharedPreferences。</p>
<p>3、 使用Messenger</p>
<p>Messenger是一种轻量级的IPC方案，它的底层实现就是AIDL。Messenger是以串行的方式处理请求的，即服务端只能一个个处理，不存在并发执行的情形。</p>
<p>4、 使用AIDL</p>
<p>大致流程：首先建一个Service和一个AIDL接口，接着创建一个类继承自AIDL接口中的Stub类中的抽象方法，在Service的onBind方法中返回这个类的对象，然后客户端就可以绑定服务端Service，建立连接后就可以访问远程服务端的方法了。</p>
<ol>
<li><p>AIDL支持的数据类型：基本数据类型、String和CharSequence、ArrayList、HashMap、Parcelable以及AIDL；</p>
</li>
<li><p>某些类即使和AIDL文件在同一个包中也要显式import进来；</p>
</li>
<li><p>AIDL中除了基本数据类，其他类型的参数都要标上方向：in、out或者inout；</p>
</li>
<li><p>AIDL接口中支持方法，不支持声明静态变量；</p>
</li>
<li><p>为了方便AIDL的开发，建议把所有和AIDL相关的类和文件全部放入同一个包中，这样做的好处是，当客户端是另一个应用的时候，可以直接把整个包复制到客户端工程中。</p>
</li>
<li><p>RemoteCallbackList是系统专门提供的用于删除跨进程Listener的接口。RemoteCallbackList是一个泛型，支持管理任意的AIDL接口，因为所有的AIDL接口都继承自IInterface接口。</p>
<p>​</p>
</li>
</ol>
<p>5、使用ContentProvider</p>
<ol>
<li><p>ContentProvider主要以表格的形式来组织数据，并且可以包含多个表；</p>
</li>
<li><p>ContentProvider还支持文件数据，比如图片、视频等，系统提供的MediaStore就是文件类型的ContentProvider；</p>
</li>
<li><p>ContentProvider对底层的数据存储方式没有任何要求，可以是SQLite、文件，甚至是内存中的一个对象都行；</p>
</li>
<li><p>要观察ContentProvider中的数据变化情况，可以通过ContentResolver的registerContentObserver方法来注册观察者；</p>
<p>​</p>
</li>
</ol>
<p>6、使用Socket</p>
<p>套接字，分为流式套接字和用户数据报套接字两种，分别对应于网络的传输控制层中TCP和UDP协议。</p>
<ul>
<li>TCP协议是面向连接的协议，提供稳定的双向通信功能，TCP连接的建立需要经过”三次握手”才能完成，为了提供稳定的数据传输功能，其本身提供了超时重传功能，因此具有很高的稳定性</li>
<li>UDP是无连接的，提供不稳定的单向通信功能，当然UDP也可以实现双向通信功能，在性能上，UDP具有更好的效率，其缺点是不保证数据能够正确传输，尤其是在网络拥塞的情况下。</li>
</ul>
<h2 id="Binder连接池"><a href="#Binder连接池" class="headerlink" title="Binder连接池"></a>Binder连接池</h2><ul>
<li><p>当项目规模很大的时候，创建很多个Service是不对的做法，因为service是系统资源，太多的service会使得应用看起来很重，所以最好是将所有的AIDL放在同一个Service中去管理。整个工作机制是：每个业务模块创建自己的AIDL接口并实现此接口，这个时候不同业务模块之间是不能有耦合的，所有实现细节我们要单独开来，然后向服务端提供自己的唯一标识和其对应的Binder对象；对于服务端来说，只需要一个Service，服务端提供一个queryBinder接口，这个接口能够根据业务模块的特征来返回相应的Binder对象给它们，不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。<br>Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service去执行，从而避免了重复创建Service的过程。</p>
</li>
<li><p>建议在AIDL开发工作中引入BinderPool机制。当新业务模块加入新的AIDL，那么在它实现自己的AIDL接口后，只需要修改BinderPoolImpl中的queryBinder方法，给自己添加一个新的binderCode并返回相对应的Binder对象即可，不需要添加新的Service。</p>
<p>​</p>
</li>
</ul>
<h3 id="选用合适的IPC方式"><a href="#选用合适的IPC方式" class="headerlink" title="选用合适的IPC方式"></a>选用合适的IPC方式</h3><p><img src="http://hujiaweibujidao.github.io/images/androidart_ipc.png" alt="Mou icon"></p>

        
        </div>
        <footer class="article-footer">
            <div class="share-container">



</div>

    <a data-url="http://yoursite.com/2017/03/07/android/books/《Android开发艺术探索》Chap2_IPC机制/" data-id="cj6oqpm0d000yyjpjt99opnpu" class="article-share-link"><i class="fa fa-share"></i>Share</a>
<script>
    (function ($) {
        // Prevent duplicate binding
        if (typeof(__SHARE_BUTTON_BINDED__) === 'undefined' || !__SHARE_BUTTON_BINDED__) {
            __SHARE_BUTTON_BINDED__ = true;
        } else {
            return;
        }
        $('body').on('click', function() {
            $('.article-share-box.on').removeClass('on');
        }).on('click', '.article-share-link', function(e) {
            e.stopPropagation();

            var $this = $(this),
                url = $this.attr('data-url'),
                encodedUrl = encodeURIComponent(url),
                id = 'article-share-box-' + $this.attr('data-id'),
                offset = $this.offset(),
                box;

            if ($('#' + id).length) {
                box = $('#' + id);

                if (box.hasClass('on')){
                    box.removeClass('on');
                    return;
                }
            } else {
                var html = [
                    '<div id="' + id + '" class="article-share-box">',
                        '<input class="article-share-input" value="' + url + '">',
                        '<div class="article-share-links">',
                            '<a href="https://twitter.com/intent/tweet?url=' + encodedUrl + '" class="fa fa-twitter article-share-twitter" target="_blank" title="Twitter"></a>',
                            '<a href="https://www.facebook.com/sharer.php?u=' + encodedUrl + '" class="fa fa-facebook article-share-facebook" target="_blank" title="Facebook"></a>',
                            '<a href="http://pinterest.com/pin/create/button/?url=' + encodedUrl + '" class="fa fa-pinterest article-share-pinterest" target="_blank" title="Pinterest"></a>',
                            '<a href="https://plus.google.com/share?url=' + encodedUrl + '" class="fa fa-google article-share-google" target="_blank" title="Google+"></a>',
                        '</div>',
                    '</div>'
                ].join('');

              box = $(html);

              $('body').append(box);
            }

            $('.article-share-box.on').hide();

            box.css({
                top: offset.top + 25,
                left: offset.left
            }).addClass('on');

        }).on('click', '.article-share-box', function (e) {
            e.stopPropagation();
        }).on('click', '.article-share-box-input', function () {
            $(this).select();
        }).on('click', '.article-share-box-link', function (e) {
            e.preventDefault();
            e.stopPropagation();

            window.open(this.href, 'article-share-box-window-' + Date.now(), 'width=500,height=450');
        });
    })(jQuery);
</script>

            
    
        <a href="http://yoursite.com/2017/03/07/android/books/《Android开发艺术探索》Chap2_IPC机制/#comments" class="article-comment-link disqus-comment-count" data-disqus-url="http://yoursite.com/2017/03/07/android/books/《Android开发艺术探索》Chap2_IPC机制/">Comments</a>
    

        </footer>
    </div>
    
</article>



    <article id="post-android/books/《Android开发艺术探索》Chap1_Activity生命周期和启动模式" class="article article-type-post" itemscope itemprop="blogPost">
    <div class="article-inner">
        
        
            <header class="article-header">
                
    
        <h1 itemprop="name">
            <a class="article-title" href="/2017/03/07/android/books/《Android开发艺术探索》Chap1_Activity生命周期和启动模式/">《Android开发艺术探索》Chap1_Activity生命周期和启动模式</a>
        </h1>
    

                
                    <div class="article-meta">
                        
    <div class="article-date">
        <i class="fa fa-calendar"></i>
        <a href="/2017/03/07/android/books/《Android开发艺术探索》Chap1_Activity生命周期和启动模式/">
            <time datetime="2017-03-07T09:01:33.000Z" itemprop="datePublished">2017-03-07</time>
        </a>
    </div>


                        
    <div class="article-category">
    	<i class="fa fa-folder"></i>
        <a class="article-category-link" href="/categories/android/">android</a><i class="fa fa-angle-right"></i><a class="article-category-link" href="/categories/android/books/">books</a><i class="fa fa-angle-right"></i><a class="article-category-link" href="/categories/android/books/《Android开发艺术探索》/">《Android开发艺术探索》</a>
    </div>

                        
    <div class="article-tag">
        <i class="fa fa-tag"></i>
        <a class="tag-link" href="/tags/android/">android</a>, <a class="tag-link" href="/tags/books/">books</a>
    </div>

                    </div>
                
            </header>
        
        
        <div class="article-entry" itemprop="articleBody">
        
            
            <p>注：此篇笔记只记录重难点，对于基础和详细内容请自行学习《Android开发艺术探索》</p>
<hr>
<h2 id="1-1-Activity的生命周期"><a href="#1-1-Activity的生命周期" class="headerlink" title="1.1 Activity的生命周期"></a>1.1 Activity的生命周期</h2><h3 id="1-1-1-典型情况下的生命周期"><a href="#1-1-1-典型情况下的生命周期" class="headerlink" title="1.1.1 典型情况下的生命周期"></a>1.1.1 典型情况下的生命周期</h3><ul>
<li><p>onStart和onResume的区别是onStart可见，还没有出现在前台，无法和用户进行交互。onResume获取到焦点可以和用户交互。</p>
</li>
<li><p>当启动新Activity是透明主题时，旧Activity不会走onStop。</p>
</li>
<li><p>Activity切换时，旧Activity的onPause会先执行，然后才会启动新的Activity。</p>
</li>
</ul>
<h3 id="1-1-2-异常情况下的生命周期"><a href="#1-1-2-异常情况下的生命周期" class="headerlink" title="1.1.2 异常情况下的生命周期"></a>1.1.2 异常情况下的生命周期</h3><ul>
<li><p>Activity在异常情况下被回收时，onSaveInstanceState方法会被回调，回调时机是在onStop之前，当Activity被重新创建的时候，onRestoreInstanceState方法会被回调，时序在onStart之后；</p>
</li>
<li><p>当Activity异常情况下的情况下需要重新创建时，系统会默认我们保存当前Activity的视图结果，并在Activity重启后为我们恢复这些数据，比如文本框用户输入的数据，ListView的滚动位置等，具体可以查看相对应View的源码，查看onSaveInstanceState和onRestoreInstanceState方法。</p>
</li>
<li><p>关于保存和恢复View的层次结构，系统的<strong>工作流程</strong>：首先Activity会调用onSaveInstanceState去保存数据，然后Activity会<strong>委托</strong>Window去保存数据，接着Window会委托它上面的顶级容器去保存数据，顶层容器一般是DecorView（ViewGroup)；最后顶层容器再去一一通知它的子元素保存数据，这样整个数据的保存过程就完成了。</p>
</li>
<li>如果资源内存不足优先级低的Activity会被杀死，<strong>优先级</strong>从高到低：</li>
<li>前台Activity-正在和用户交互优先级最高；</li>
<li>可见但非前台的Activity-比如Activity中弹了一个对话框，导致Activty可见但位于后台；</li>
<li><p>后台Activity-已经被暂停的Activity，已经执行了onStop，优先级最低。</p>
</li>
<li><p>如果我们不想配置发生改变就Activity重新创建，可以使用Activity的<strong>configChanges</strong>属性；常用的有locale、orientation和keyboardHidden这三个选项；指定了configChanges后，Activity发生对应改变后，不会重启Activity，只会调用onConfigurationChanged方法。</p>
</li>
</ul>
<h2 id="1-2-Activity的启动模式"><a href="#1-2-Activity的启动模式" class="headerlink" title="1.2 Activity的启动模式"></a>1.2 Activity的启动模式</h2><h3 id="1-2-1-Activity的LaunchMode"><a href="#1-2-1-Activity的LaunchMode" class="headerlink" title="1.2.1 Activity的LaunchMode"></a>1.2.1 Activity的LaunchMode</h3><p><strong>任务栈</strong>是一个“后进先出”的栈结构，每次finish()处于前台的Activity就会出栈，直到栈为空为止，当栈中无任何Activity的时候，系统就会回收这个任务栈。</p>
<ul>
<li><p><strong>standard</strong>：标准模式，系统默认模式，每次启动都会创建一个新的实例；在这种模式下，谁启动了这个Activity，这个Activity就在启动它的那个Activity所在的栈中。当我们使用ApplicationContext去启动standard模式的Activity就会报错，因为standard模式的Activity会默认进入启动它的Activity所属的任务栈中，而非Activity类型的Context并没有任务栈。解决的办法是为这个待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位，这样启动的时候就会为它创建一个新的任务栈。</p>
</li>
<li><p><strong>singleTop</strong>: 栈顶复用模式。这种模式下，如果新的Activity已经位于栈顶，那么此Activity不会创建，同时他的onNewIntent方法会被回调，onCreate、onStart不会被调用。如果新的Activity的实例存在但不是位于栈顶，那么新的Activty依然会重新创建。</p>
</li>
<li><p><strong>singleTask</strong>: 栈内复用模式。这是一种单实例模式，这种模式下，Activity在一个栈中存在，那么多次启动该Activity都不会重新创建实例，和sinleTop一样，系统会回调其onNewIntent。如果启动的Activity没有所需要的任务栈，就会先创建任务栈再创建Activity。singleTask默认具有clearTop的效果，具有该模式的Activity会让其之上的Activity全部出栈。</p>
</li>
<li><p><strong>singleInstance</strong>: 单实例模式。这是一种加强的singleTask模式，除了具备singleTask的特性之外，具有该模式的Activity只能单独位于一个任务栈中；比如Activity A是singleInstance模式的，当A启动后，系统会为它创建一个新的任务栈，后续的启动均不会创建新的Activity，除非这个任务栈被系统销毁了。</p>
</li>
<li><p>参数TaskAffinity用于指定Activity栈，TaskAffinity属性经常和singleTask启动模式或allowTaskReparenting属性配对使用，其他情况没有意义。当应用A启动了应用B的某个Activity后，如果这个Activity的allowTaskReparenting属性为true的话，那么当应用B被启动后，此Activity会直接从应用A的任务栈转移到应用B的任务栈中。</p>
</li>
<li><p>两种方法都可以为Activity指定启动模式，但还是有区别。</p>
</li>
<li><p>Activity的启动模式可以通过AndroidMenifest为其指定启动模式</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">&lt;activity android:name=&quot;.MainActivity&quot; android:launchMode=&quot;singleTask&quot; /&gt;</div></pre></td></tr></table></figure>
</li>
<li><p>还可以通过在Intent中设置标记位来为Activity指定启动模式</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">Intent intent = new Intent(this,MainActivity.class); </div><div class="line">intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);</div><div class="line"> startActivity(intent);</div></pre></td></tr></table></figure>
</li>
<li><p>第二种方式的<strong>优先级</strong>高于第一种，如果都设置了只有第二种会生效。第一种方式无法直接为Activity设置FLAG_ACTIVITY_CLEAR_TOP标记，而第二种方式无法为Activity指定singleInstance模式。</p>
</li>
<li><p>通过adb shell dumpsys activity命令可以详细的了解当前任务栈情况。</p>
</li>
</ul>
<h3 id="1-2-2-Activity的Flags"><a href="#1-2-2-Activity的Flags" class="headerlink" title="1.2.2 Activity的Flags"></a>1.2.2 Activity的Flags</h3><ul>
<li><p>FLAG_ACTIVITY_NEW_TASK为Activity指定“singleTask”启动模式，其效果和xml中指定该启动模式相同。</p>
</li>
<li><p>FLAG_ACTIVITY_SINGLE_TOP为Activity指定“singleTop”启动模式，其效果和xml中指定该启动模式相同。</p>
</li>
<li><p>FLAG_ACTIVITY_CLEAR_TOP具有此标记位的Activity，当他启动时，在同一个任务栈中所有位于它上面的Activity都要出栈。这个模式一般与FLAG_ACTIVITY_NEW_TASK配合使用，singleTask启动模式默认具有此标记为的效果。</p>
</li>
<li><p>FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS具有这个标记为的Activity不会出现在历史Activity的列表中，当某些情况不希望用户通过历史列表回到我们Activity的时候这边标记比较有用。等同于在XML中指定Activity的属性android:excludeFromRecents=”true”。</p>
</li>
</ul>
<h2 id="1-3-IntentFilter的匹配规则"><a href="#1-3-IntentFilter的匹配规则" class="headerlink" title="1.3 IntentFilter的匹配规则"></a>1.3 IntentFilter的匹配规则</h2><p>启动Activity分为两种，显式调用（明确地指定被启动对象的组件信息，包括包名和类名）和隐式调用（不需要明确指定组件信息，需要Intent能匹配上目标组件的IntentFilter中所设置的过滤信息）。IntentFilter的过滤信息有action、category、data。为了匹配过滤列表，需同时匹配过滤列表中的action、category、data信息，否则匹配失败；一个Activity中可以有多个intent-filter，一个Intent只要能匹配任何一组intent-filter即可成功启动对应的Activity。</p>
<ul>
<li><p>action匹配规则：要求intent中的action存在且必须和过滤规则中的其中一个相同，区分大小写。</p>
</li>
<li><p>category匹配规则：系统会默认加上一个android.intent.category.DEAFAULT，所以intent中可以不存在category，但如果存在就必须匹配其中一个。同时为了我们的Activity能够支持隐式调用，就必须要在intent-filter中指定“android.intent.category.DEFAULT”这个category。</p>
</li>
<li><p>data匹配规则：data由两部分组成，mimeType和URI，要求和action相似。如果没有指定URI，URI但默认值为content和file（schema）</p>
</li>
<li><p>如果要为Intent指定完整的data，必须调用setDataAndType方法，不能先调用setData再调用setType，因为这两个方法都会清除对方的值。</p>
</li>
<li><p>采用PackageManager的resolveActivity方法或Intent的resolveActivity方法，如果找不到匹配的Activity就会返回null，我们通过判断返回值就可以规避抛出android.content.ActivityNotFoundException异常。</p>
</li>
<li><p>在intent-filter中声明了<category android:name="android.intent.category.DEFAULT">这个category的Activity，才可以接收隐式意图。</category></p>
</li>
<li><p>有一类action和category的共同作用是标明这是一个入口Activity。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">&lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;</div><div class="line">&lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;</div></pre></td></tr></table></figure></li>
</ul>

        
        </div>
        <footer class="article-footer">
            <div class="share-container">



</div>

    <a data-url="http://yoursite.com/2017/03/07/android/books/《Android开发艺术探索》Chap1_Activity生命周期和启动模式/" data-id="cj6oqpm0e000zyjpj380py0fr" class="article-share-link"><i class="fa fa-share"></i>Share</a>
<script>
    (function ($) {
        // Prevent duplicate binding
        if (typeof(__SHARE_BUTTON_BINDED__) === 'undefined' || !__SHARE_BUTTON_BINDED__) {
            __SHARE_BUTTON_BINDED__ = true;
        } else {
            return;
        }
        $('body').on('click', function() {
            $('.article-share-box.on').removeClass('on');
        }).on('click', '.article-share-link', function(e) {
            e.stopPropagation();

            var $this = $(this),
                url = $this.attr('data-url'),
                encodedUrl = encodeURIComponent(url),
                id = 'article-share-box-' + $this.attr('data-id'),
                offset = $this.offset(),
                box;

            if ($('#' + id).length) {
                box = $('#' + id);

                if (box.hasClass('on')){
                    box.removeClass('on');
                    return;
                }
            } else {
                var html = [
                    '<div id="' + id + '" class="article-share-box">',
                        '<input class="article-share-input" value="' + url + '">',
                        '<div class="article-share-links">',
                            '<a href="https://twitter.com/intent/tweet?url=' + encodedUrl + '" class="fa fa-twitter article-share-twitter" target="_blank" title="Twitter"></a>',
                            '<a href="https://www.facebook.com/sharer.php?u=' + encodedUrl + '" class="fa fa-facebook article-share-facebook" target="_blank" title="Facebook"></a>',
                            '<a href="http://pinterest.com/pin/create/button/?url=' + encodedUrl + '" class="fa fa-pinterest article-share-pinterest" target="_blank" title="Pinterest"></a>',
                            '<a href="https://plus.google.com/share?url=' + encodedUrl + '" class="fa fa-google article-share-google" target="_blank" title="Google+"></a>',
                        '</div>',
                    '</div>'
                ].join('');

              box = $(html);

              $('body').append(box);
            }

            $('.article-share-box.on').hide();

            box.css({
                top: offset.top + 25,
                left: offset.left
            }).addClass('on');

        }).on('click', '.article-share-box', function (e) {
            e.stopPropagation();
        }).on('click', '.article-share-box-input', function () {
            $(this).select();
        }).on('click', '.article-share-box-link', function (e) {
            e.preventDefault();
            e.stopPropagation();

            window.open(this.href, 'article-share-box-window-' + Date.now(), 'width=500,height=450');
        });
    })(jQuery);
</script>

            
    
        <a href="http://yoursite.com/2017/03/07/android/books/《Android开发艺术探索》Chap1_Activity生命周期和启动模式/#comments" class="article-comment-link disqus-comment-count" data-disqus-url="http://yoursite.com/2017/03/07/android/books/《Android开发艺术探索》Chap1_Activity生命周期和启动模式/">Comments</a>
    

        </footer>
    </div>
    
</article>



    <article id="post-android/code/H5启动Android App" class="article article-type-post" itemscope itemprop="blogPost">
    <div class="article-inner">
        
        
            <header class="article-header">
                
    
        <h1 itemprop="name">
            <a class="article-title" href="/2017/03/07/android/code/H5启动Android App/">H5启动Android App</a>
        </h1>
    

                
                    <div class="article-meta">
                        
    <div class="article-date">
        <i class="fa fa-calendar"></i>
        <a href="/2017/03/07/android/code/H5启动Android App/">
            <time datetime="2017-03-07T01:58:25.000Z" itemprop="datePublished">2017-03-07</time>
        </a>
    </div>


                        
    <div class="article-category">
    	<i class="fa fa-folder"></i>
        <a class="article-category-link" href="/categories/android/">android</a><i class="fa fa-angle-right"></i><a class="article-category-link" href="/categories/android/code/">code</a>
    </div>

                        
    <div class="article-tag">
        <i class="fa fa-tag"></i>
        <a class="tag-link" href="/tags/android/">android</a>, <a class="tag-link" href="/tags/code/">code</a>
    </div>

                    </div>
                
            </header>
        
        
        <div class="article-entry" itemprop="articleBody">
        
            
            <blockquote>
<p>首先明晰，<a href="http://lib.csdn.net/base/wechat" target="_blank" rel="external">微信</a>里屏蔽了schema协议。除非你是微信的合作伙伴之类的，他们专门给你配置进白名单。否则，我们就没办法通过这个协议在微信中直接唤起app。</p>
<p>因此，我们需要先判断页面场景是否在微信中，如果在微信中，则会提示用户在浏览器中打开。</p>
</blockquote>
<h3 id="H5中间接判断应用是否安装"><a href="#H5中间接判断应用是否安装" class="headerlink" title="H5中间接判断应用是否安装"></a>H5中间接判断应用是否安装</h3><blockquote>
<p>这里的逻辑很简单，当没有成功打开app的时候，新页面不会弹出则页面逻辑继续进行；否则如果进入了新页面，则页面逻辑便终止了。所以我们可以另开一个延时的线程来判断这个事情</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">if(...)&#123;</div><div class="line">document.location = &apos;&apos;;</div><div class="line">setTimeout(function()&#123;</div><div class="line">   //此处如果执行则表示没有app</div><div class="line">&#125;,200);</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h3 id="通过H5唤起APP"><a href="#通过H5唤起APP" class="headerlink" title="通过H5唤起APP"></a>通过H5唤起APP</h3><blockquote>
<p>编辑AndroidManifest.xml，主要是增加第二个<intent-filter>，launchapp用来标识schema，最好能保证手机系统唯一，那样就可以打开应用，而不是弹出一个选择框。可以附带自己的数据通过string传递到activity，比如完整url为 launchapp://?data=mydata</intent-filter></p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line">&lt;activity    </div><div class="line">     android:name=&quot;com.robert.MainActivity&quot;    </div><div class="line">     android:configChanges=&quot;orientation|keyboardHidden|navigation|screenSize&quot;    </div><div class="line">     android:screenOrientation=&quot;landscape&quot;    </div><div class="line">     android:theme=&quot;@android:style/Theme.NoTitleBar.Fullscreen&quot; &gt;    </div><div class="line">     &lt;intent-filter&gt;    </div><div class="line">         &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;    </div><div class="line">         &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;    </div><div class="line">     &lt;/intent-filter&gt;    </div><div class="line">     &lt;intent-filter&gt;    </div><div class="line">         &lt;action android:name=&quot;android.intent.action.VIEW&quot; /&gt;    </div><div class="line">         &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; /&gt;    </div><div class="line">         &lt;category android:name=&quot;android.intent.category.DEFAULT&quot;/&gt;    </div><div class="line">         &lt;data android:scheme=&quot;launchapp&quot; android:pathPrefix=&quot;/haha&quot; /&gt;    </div><div class="line">     &lt;/intent-filter&gt;    </div><div class="line">   &lt;/activity&gt;</div></pre></td></tr></table></figure>
<blockquote>
<p>然后通过activity获得data数据：</p>
</blockquote>
   <figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">     public void onCreate(Bundle savedInstanceState) &#123;             </div><div class="line">     	Uri uridata = this.getIntent().getData();             </div><div class="line">     	String mydata = uridata.getQueryParameter(&quot;data&quot;);            </div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h3 id="编写html页面"><a href="#编写html页面" class="headerlink" title="编写html页面"></a>编写html页面</h3><blockquote>
<p>整个页面也许是某个app的详细介绍，这里只写出关键的js代码：</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line">&lt;activity  </div><div class="line">     android:name=&quot;com.robert.MainActivity&quot;    </div><div class="line">     android:configChanges=&quot;orientation|keyboardHidden|navigation|screenSize&quot;    </div><div class="line">     android:screenOrientation=&quot;landscape&quot;    </div><div class="line">     android:theme=&quot;@android:style/Theme.NoTitleBar.Fullscreen&quot; &gt;    </div><div class="line">     &lt;intent-filter&gt;    </div><div class="line">         &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;    </div><div class="line">         &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;    </div><div class="line">     &lt;/intent-filter&gt;    </div><div class="line">     &lt;intent-filter&gt;    </div><div class="line">         &lt;action android:name=&quot;android.intent.action.VIEW&quot; /&gt;    </div><div class="line">         &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; /&gt;    </div><div class="line">         &lt;category android:name=&quot;android.intent.category.DEFAULT&quot;/&gt;    </div><div class="line">         &lt;data android:scheme=&quot;launchapp&quot; android:pathPrefix=&quot;/haha&quot; /&gt;    </div><div class="line">     &lt;/intent-filter&gt;    </div><div class="line">&lt;/activity&gt;</div></pre></td></tr></table></figure>
<blockquote>
<p>下面代码可以达到这样一个目的，先请求 launchapp:// ,如果系统能处理，或者说已经安装了myapp表示的应用，那么就可以打开，另外，如果不能打开，直接刷新一下当前页面，等于是重置location。</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line">function openApp() &#123;    </div><div class="line">       </div><div class="line">           if (/android/i.test(navigator.userAgent)) &#123;    </div><div class="line">                var isrefresh = getUrlParam(&apos;refresh&apos;); // 获得refresh参数    </div><div class="line">                if(isrefresh == 1) &#123;    </div><div class="line">                    return    </div><div class="line">                &#125;    </div><div class="line">                window.location.href = &apos;launchapp://haha?data=mydata&apos;;    </div><div class="line">                window.setTimeout(function () &#123;    </div><div class="line">                        window.location.href += &apos;&amp;refresh=1&apos; // 附加一个特殊参数，用来标识这次刷新不要再调用myapp:// 了    </div><div class="line">                &#125;, 500);    </div><div class="line">            &#125;    </div><div class="line">       </div><div class="line">   &#125;</div></pre></td></tr></table></figure>
<h3 id="整体代码"><a href="#整体代码" class="headerlink" title="整体代码"></a>整体代码</h3><blockquote>
<p>代码功能: 判断手机/平板是否安装app 如果安装 则调用app的scheme，传入url当作参数，来做后续操作 如果没有安装 则跳转到app store/google play 下载app</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line">(function () &#123;</div><div class="line">    var openUrl = window.location.search;</div><div class="line">    try &#123;</div><div class="line">        openUrl = openUrl.substring(1, openUrl.length);</div><div class="line">    &#125; catch (e) &#123;&#125;</div><div class="line">    var isiOS = navigator.userAgent.match(&apos;iPad&apos;) || navigator.userAgent.match(&apos;iPhone&apos;) || navigator.userAgent.match(</div><div class="line">        &apos;iPod&apos;),</div><div class="line">        isAndroid = navigator.userAgent</div><div class="line">            .match(&apos;Android&apos;),</div><div class="line">        isDesktop = !isiOS &amp;&amp; !isAndroid;</div><div class="line">    if (isiOS) &#123;</div><div class="line">        setTimeout(function () &#123;</div><div class="line">            window.location = &quot;itms-apps://itunes.apple.com/app/[name]/[id]?mt=8&quot;;</div><div class="line">        &#125;, 25);</div><div class="line">        window.location = &quot;[scheme]://[host]?url=&quot; + openUrl;</div><div class="line">    &#125; else if (isAndroid) &#123;</div><div class="line">        window.location = &quot;intent://[host]/&quot; + &quot;url=&quot; + openUrl + &quot;#Intent;scheme=[scheme];package=[package_name];end&quot;;</div><div class="line">    &#125; else &#123;</div><div class="line">        window.location.href = openUrl;</div><div class="line">    &#125;</div><div class="line">&#125;)();</div></pre></td></tr></table></figure>
        
        </div>
        <footer class="article-footer">
            <div class="share-container">



</div>

    <a data-url="http://yoursite.com/2017/03/07/android/code/H5启动Android App/" data-id="cj6oqpm0y0014yjpjktsezcnz" class="article-share-link"><i class="fa fa-share"></i>Share</a>
<script>
    (function ($) {
        // Prevent duplicate binding
        if (typeof(__SHARE_BUTTON_BINDED__) === 'undefined' || !__SHARE_BUTTON_BINDED__) {
            __SHARE_BUTTON_BINDED__ = true;
        } else {
            return;
        }
        $('body').on('click', function() {
            $('.article-share-box.on').removeClass('on');
        }).on('click', '.article-share-link', function(e) {
            e.stopPropagation();

            var $this = $(this),
                url = $this.attr('data-url'),
                encodedUrl = encodeURIComponent(url),
                id = 'article-share-box-' + $this.attr('data-id'),
                offset = $this.offset(),
                box;

            if ($('#' + id).length) {
                box = $('#' + id);

                if (box.hasClass('on')){
                    box.removeClass('on');
                    return;
                }
            } else {
                var html = [
                    '<div id="' + id + '" class="article-share-box">',
                        '<input class="article-share-input" value="' + url + '">',
                        '<div class="article-share-links">',
                            '<a href="https://twitter.com/intent/tweet?url=' + encodedUrl + '" class="fa fa-twitter article-share-twitter" target="_blank" title="Twitter"></a>',
                            '<a href="https://www.facebook.com/sharer.php?u=' + encodedUrl + '" class="fa fa-facebook article-share-facebook" target="_blank" title="Facebook"></a>',
                            '<a href="http://pinterest.com/pin/create/button/?url=' + encodedUrl + '" class="fa fa-pinterest article-share-pinterest" target="_blank" title="Pinterest"></a>',
                            '<a href="https://plus.google.com/share?url=' + encodedUrl + '" class="fa fa-google article-share-google" target="_blank" title="Google+"></a>',
                        '</div>',
                    '</div>'
                ].join('');

              box = $(html);

              $('body').append(box);
            }

            $('.article-share-box.on').hide();

            box.css({
                top: offset.top + 25,
                left: offset.left
            }).addClass('on');

        }).on('click', '.article-share-box', function (e) {
            e.stopPropagation();
        }).on('click', '.article-share-box-input', function () {
            $(this).select();
        }).on('click', '.article-share-box-link', function (e) {
            e.preventDefault();
            e.stopPropagation();

            window.open(this.href, 'article-share-box-window-' + Date.now(), 'width=500,height=450');
        });
    })(jQuery);
</script>

            
    
        <a href="http://yoursite.com/2017/03/07/android/code/H5启动Android App/#comments" class="article-comment-link disqus-comment-count" data-disqus-url="http://yoursite.com/2017/03/07/android/code/H5启动Android App/">Comments</a>
    

        </footer>
    </div>
    
</article>



    <article id="post-node/books/7days/01_getting_started" class="article article-type-post" itemscope itemprop="blogPost">
    <div class="article-inner">
        
        
            <header class="article-header">
                
    
        <h1 itemprop="name">
            <a class="article-title" href="/2016/03/23/node/books/7days/01_getting_started/">七天学会NodeJS - NodeJS基础</a>
        </h1>
    

                
                    <div class="article-meta">
                        
    <div class="article-date">
        <i class="fa fa-calendar"></i>
        <a href="/2016/03/23/node/books/7days/01_getting_started/">
            <time datetime="2016-03-22T22:06:34.000Z" itemprop="datePublished">2016-03-23</time>
        </a>
    </div>


                        
    <div class="article-category">
    	<i class="fa fa-folder"></i>
        <a class="article-category-link" href="/categories/node/">node</a><i class="fa fa-angle-right"></i><a class="article-category-link" href="/categories/node/books/">books</a><i class="fa fa-angle-right"></i><a class="article-category-link" href="/categories/node/books/7days/">7days</a>
    </div>

                        
    <div class="article-tag">
        <i class="fa fa-tag"></i>
        <a class="tag-link" href="/tags/7days/">7days</a>, <a class="tag-link" href="/tags/books/">books</a>, <a class="tag-link" href="/tags/node/">node</a>
    </div>

                    </div>
                
            </header>
        
        
        <div class="article-entry" itemprop="articleBody">
        
            
            <h2 id="七天学会NodeJS-NodeJS基础"><a href="#七天学会NodeJS-NodeJS基础" class="headerlink" title="七天学会NodeJS - NodeJS基础"></a>七天学会NodeJS - NodeJS基础</h2><h3 id="什么是NodeJS"><a href="#什么是NodeJS" class="headerlink" title="什么是NodeJS"></a>什么是NodeJS</h3><p>JS是脚本语言，脚本语言都需要一个解析器才能运行。对于写在HTML页面里的JS，浏览器充当了解析器的角色。而对于需要独立运行的JS，NodeJS就是一个解析器。</p>
<p>每一种解析器都是一个运行环境，不但允许JS定义各种数据结构，进行各种计算，还允许JS使用运行环境提供的内置对象和方法做一些事情。例如运行在浏览器中的JS的用途是操作DOM，浏览器就提供了<code>document</code>之类的内置对象。而运行在NodeJS中的JS的用途是操作磁盘文件或搭建HTTP服务器，NodeJS就相应提供了<code>fs</code>、<code>http</code>等内置对象。</p>
<h3 id="有啥用处"><a href="#有啥用处" class="headerlink" title="有啥用处"></a>有啥用处</h3><p>尽管存在一听说可以直接运行JS文件就觉得很酷的同学，但大多数同学在接触新东西时首先关心的是有啥用处，以及能带来啥价值。</p>
<p>NodeJS的作者说，他创造NodeJS的目的是为了实现高性能Web服务器，他首先看重的是事件机制和异步IO模型的优越性，而不是JS。但是他需要选择一种编程语言实现他的想法，这种编程语言不能自带IO功能，并且需要能良好支持事件机制。JS没有自带IO功能，天生就用于处理浏览器中的DOM事件，并且拥有一大群程序员，因此就成为了天然的选择。</p>
<p>如他所愿，NodeJS在服务端活跃起来，出现了大批基于NodeJS的Web服务。而另一方面，NodeJS让前端众如获神器，终于可以让自己的能力覆盖范围跳出浏览器窗口，更大批的前端工具如雨后春笋。</p>
<p>因此，对于前端而言，虽然不是人人都要拿NodeJS写一个服务器程序，但简单可至使用命令交互模式调试JS代码片段，复杂可至编写工具提升工作效率。</p>
<p>NodeJS生态圈正欣欣向荣。</p>
<h3 id="如何安装"><a href="#如何安装" class="headerlink" title="如何安装"></a>如何安装</h3><h4 id="安装程序"><a href="#安装程序" class="headerlink" title="安装程序"></a>安装程序</h4><p>NodeJS提供了一些安装程序，都可以在<a href="http://nodejs.org/download/" target="_blank" rel="external">nodejs.org</a>这里下载并安装。</p>
<p>Windows系统下，选择和系统版本匹配的<code>.msi</code>后缀的安装文件。Mac OS X系统下，选择<code>.pkg</code>后缀的安装文件。</p>
<h4 id="编译安装"><a href="#编译安装" class="headerlink" title="编译安装"></a>编译安装</h4><p>Linux系统下没有现成的安装程序可用，虽然一些发行版可以使用<code>apt-get</code>之类的方式安装，但不一定能安装到最新版。因此Linux系统下一般使用以下方式编译方式安装NodeJS。</p>
<ol>
<li><p>确保系统下g++版本在4.6以上，python版本在2.6以上。</p>
</li>
<li><p>从<a href="http://nodejs.org/download/" target="_blank" rel="external">nodejs.org</a>下载<code>tar.gz</code>后缀的NodeJS最新版源代码包并解压到某个位置。</p>
</li>
<li><p>进入解压到的目录，使用以下命令编译和安装。</p>
<pre><code>$ ./configure
$ make
$ sudo make install
</code></pre></li>
</ol>
<h3 id="如何运行"><a href="#如何运行" class="headerlink" title="如何运行"></a>如何运行</h3><p>打开终端，键入<code>node</code>进入命令交互模式，可以输入一条代码语句后立即执行并显示结果，例如：</p>
<pre><code>$ node
&gt; console.log(&apos;Hello World!&apos;);
Hello World!
</code></pre><p>如果要运行一大段代码的话，可以先写一个JS文件再运行。例如有以下<code>hello.js</code>。</p>
<pre><code>function hello() {
    console.log(&apos;Hello World!&apos;);
}
hello();
</code></pre><p>写好后在终端下键入<code>node hello.js</code>运行，结果如下：</p>
<pre><code>$ node hello.js
Hello World!
</code></pre><h4 id="权限问题"><a href="#权限问题" class="headerlink" title="权限问题"></a>权限问题</h4><p>在Linux系统下，使用NodeJS监听80或443端口提供HTTP(S)服务时需要root权限，有两种方式可以做到。</p>
<p>一种方式是使用<code>sudo</code>命令运行NodeJS。例如通过以下命令运行的<code>server.js</code>中有权限使用80和443端口。一般推荐这种方式，可以保证仅为有需要的JS脚本提供root权限。</p>
<pre><code>$ sudo node server.js
</code></pre><p>另一种方式是使用<code>chmod +s</code>命令让NodeJS总是以root权限运行，具体做法如下。因为这种方式让任何JS脚本都有了root权限，不太安全，因此在需要很考虑安全的系统下不推荐使用。</p>
<pre><code>$ sudo chown root /usr/local/bin/node
$ sudo chmod +s /usr/local/bin/node
</code></pre><h3 id="模块"><a href="#模块" class="headerlink" title="模块"></a>模块</h3><p>编写稍大一点的程序时一般都会将代码模块化。在NodeJS中，一般将代码合理拆分到不同的JS文件中，每一个文件就是一个模块，而文件路径就是模块名。</p>
<p>在编写每个模块时，都有<code>require</code>、<code>exports</code>、<code>module</code>三个预先定义好的变量可供使用。</p>
<h4 id="require"><a href="#require" class="headerlink" title="require"></a>require</h4><p><code>require</code>函数用于在当前模块中加载和使用别的模块，传入一个模块名，返回一个模块导出对象。模块名可使用相对路径（以<code>./</code>开头），或者是绝对路径（以<code>/</code>或<code>C:</code>之类的盘符开头）。另外，模块名中的<code>.js</code>扩展名可以省略。以下是一个例子。</p>
<pre><code>var foo1 = require(&apos;./foo&apos;);
var foo2 = require(&apos;./foo.js&apos;);
var foo3 = require(&apos;/home/user/foo&apos;);
var foo4 = require(&apos;/home/user/foo.js&apos;);

// foo1至foo4中保存的是同一个模块的导出对象。
</code></pre><p>另外，可以使用以下方式加载和使用一个JSON文件。</p>
<pre><code>var data = require(&apos;./data.json&apos;);
</code></pre><h4 id="exports"><a href="#exports" class="headerlink" title="exports"></a>exports</h4><p><code>exports</code>对象是当前模块的导出对象，用于导出模块公有方法和属性。别的模块通过<code>require</code>函数使用当前模块时得到的就是当前模块的<code>exports</code>对象。以下例子中导出了一个公有方法。</p>
<pre><code>exports.hello = function () {
    console.log(&apos;Hello World!&apos;);
};
</code></pre><h4 id="module"><a href="#module" class="headerlink" title="module"></a>module</h4><p>通过<code>module</code>对象可以访问到当前模块的一些相关信息，但最多的用途是替换当前模块的导出对象。例如模块导出对象默认是一个普通对象，如果想改成一个函数的话，可以使用以下方式。</p>
<pre><code>module.exports = function () {
    console.log(&apos;Hello World!&apos;);
};
</code></pre><p>以上代码中，模块默认导出对象被替换为一个函数。</p>
<h4 id="模块初始化"><a href="#模块初始化" class="headerlink" title="模块初始化"></a>模块初始化</h4><p>一个模块中的JS代码仅在模块第一次被使用时执行一次，并在执行过程中初始化模块的导出对象。之后，缓存起来的导出对象被重复利用。</p>
<h4 id="主模块"><a href="#主模块" class="headerlink" title="主模块"></a>主模块</h4><p>通过命令行参数传递给NodeJS以启动程序的模块被称为主模块。主模块负责调度组成整个程序的其它模块完成工作。例如通过以下命令启动程序时，<code>main.js</code>就是主模块。</p>
<pre><code>$ node main.js
</code></pre><h4 id="完整示例"><a href="#完整示例" class="headerlink" title="完整示例"></a>完整示例</h4><p>例如有以下目录。</p>
<pre><code>- /home/user/hello/
    - util/
        counter.js
    main.js
</code></pre><p>其中<code>counter.js</code>内容如下：</p>
<pre><code>var i = 0;

function count() {
    return ++i;
}

exports.count = count;
</code></pre><p>该模块内部定义了一个私有变量<code>i</code>，并在<code>exports</code>对象导出了一个公有方法<code>count</code>。</p>
<p>主模块<code>main.js</code>内容如下：</p>
<pre><code>var counter1 = require(&apos;./util/counter&apos;);
var    counter2 = require(&apos;./util/counter&apos;);

console.log(counter1.count());
console.log(counter2.count());
console.log(counter2.count());
</code></pre><p>运行该程序的结果如下：</p>
<pre><code>$ node main.js
1
2
3
</code></pre><p>可以看到，<code>counter.js</code>并没有因为被require了两次而初始化两次。</p>
<h3 id="二进制模块"><a href="#二进制模块" class="headerlink" title="二进制模块"></a>二进制模块</h3><p>虽然一般我们使用JS编写模块，但NodeJS也支持使用C/C++编写二进制模块。编译好的二进制模块除了文件扩展名是<code>.node</code>外，和JS模块的使用方式相同。虽然二进制模块能使用操作系统提供的所有功能，拥有无限的潜能，但对于前端同学而言编写过于困难，并且难以跨平台使用，因此不在本教程的覆盖范围内。</p>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>本章介绍了有关NodeJS的基本概念和使用方法，总结起来有以下知识点：</p>
<ul>
<li><p>NodeJS是一个JS脚本解析器，任何操作系统下安装NodeJS本质上做的事情都是把NodeJS执行程序复制到一个目录，然后保证这个目录在系统PATH环境变量下，以便终端下可以使用<code>node</code>命令。</p>
</li>
<li><p>终端下直接输入<code>node</code>命令可进入命令交互模式，很适合用来测试一些JS代码片段，比如正则表达式。</p>
</li>
<li><p>NodeJS使用<a href="http://wiki.commonjs.org/" target="_blank" rel="external">CMD</a>模块系统，主模块作为程序入口点，所有模块在执行过程中只初始化一次。</p>
</li>
<li><p>除非JS模块不能满足需求，否则不要轻易使用二进制模块，否则你的用户会叫苦连天。</p>
</li>
</ul>

        
        </div>
        <footer class="article-footer">
            <div class="share-container">



</div>

    <a data-url="http://yoursite.com/2016/03/23/node/books/7days/01_getting_started/" data-id="cj6oqpm1n002kyjpjdo7ctm9r" class="article-share-link"><i class="fa fa-share"></i>Share</a>
<script>
    (function ($) {
        // Prevent duplicate binding
        if (typeof(__SHARE_BUTTON_BINDED__) === 'undefined' || !__SHARE_BUTTON_BINDED__) {
            __SHARE_BUTTON_BINDED__ = true;
        } else {
            return;
        }
        $('body').on('click', function() {
            $('.article-share-box.on').removeClass('on');
        }).on('click', '.article-share-link', function(e) {
            e.stopPropagation();

            var $this = $(this),
                url = $this.attr('data-url'),
                encodedUrl = encodeURIComponent(url),
                id = 'article-share-box-' + $this.attr('data-id'),
                offset = $this.offset(),
                box;

            if ($('#' + id).length) {
                box = $('#' + id);

                if (box.hasClass('on')){
                    box.removeClass('on');
                    return;
                }
            } else {
                var html = [
                    '<div id="' + id + '" class="article-share-box">',
                        '<input class="article-share-input" value="' + url + '">',
                        '<div class="article-share-links">',
                            '<a href="https://twitter.com/intent/tweet?url=' + encodedUrl + '" class="fa fa-twitter article-share-twitter" target="_blank" title="Twitter"></a>',
                            '<a href="https://www.facebook.com/sharer.php?u=' + encodedUrl + '" class="fa fa-facebook article-share-facebook" target="_blank" title="Facebook"></a>',
                            '<a href="http://pinterest.com/pin/create/button/?url=' + encodedUrl + '" class="fa fa-pinterest article-share-pinterest" target="_blank" title="Pinterest"></a>',
                            '<a href="https://plus.google.com/share?url=' + encodedUrl + '" class="fa fa-google article-share-google" target="_blank" title="Google+"></a>',
                        '</div>',
                    '</div>'
                ].join('');

              box = $(html);

              $('body').append(box);
            }

            $('.article-share-box.on').hide();

            box.css({
                top: offset.top + 25,
                left: offset.left
            }).addClass('on');

        }).on('click', '.article-share-box', function (e) {
            e.stopPropagation();
        }).on('click', '.article-share-box-input', function () {
            $(this).select();
        }).on('click', '.article-share-box-link', function (e) {
            e.preventDefault();
            e.stopPropagation();

            window.open(this.href, 'article-share-box-window-' + Date.now(), 'width=500,height=450');
        });
    })(jQuery);
</script>

            
    
        <a href="http://yoursite.com/2016/03/23/node/books/7days/01_getting_started/#comments" class="article-comment-link disqus-comment-count" data-disqus-url="http://yoursite.com/2016/03/23/node/books/7days/01_getting_started/">Comments</a>
    

        </footer>
    </div>
    
</article>



    <article id="post-node/books/7days/07_example" class="article article-type-post" itemscope itemprop="blogPost">
    <div class="article-inner">
        
        
        
        <div class="article-entry" itemprop="articleBody">
        
            
            <h2 id="七天学会NodeJS-大示例"><a href="#七天学会NodeJS-大示例" class="headerlink" title="七天学会NodeJS - 大示例"></a>七天学会NodeJS - 大示例</h2><p>学习讲究的是学以致用和融会贯通。至此我们已经分别介绍了NodeJS的很多知识点，本章作为最后一章，将完整地介绍一个使用NodeJS开发Web服务器的示例。</p>
<h3 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h3><p>我们要开发的是一个简单的静态文件合并服务器，该服务器需要支持类似以下格式的JS或CSS文件合并请求。</p>
<pre><code>http://assets.example.com/foo/??bar.js,baz.js
</code></pre><p>在以上URL中，<code>??</code>是一个分隔符，之前是需要合并的多个文件的URL的公共部分，之后是使用<code>,</code>分隔的差异部分。因此服务器处理这个URL时，返回的是以下两个文件按顺序合并后的内容。</p>
<pre><code>/foo/bar.js
/foo/baz.js
</code></pre><p>另外，服务器也需要能支持类似以下格式的普通的JS或CSS文件请求。</p>
<pre><code>http://assets.example.com/foo/bar.js
</code></pre><p>以上就是整个需求。</p>
<h3 id="第一次迭代"><a href="#第一次迭代" class="headerlink" title="第一次迭代"></a>第一次迭代</h3><p>快速迭代是一种不错的开发方式，因此我们在第一次迭代时先实现服务器的基本功能。</p>
<h4 id="设计"><a href="#设计" class="headerlink" title="设计"></a>设计</h4><p>简单分析了需求之后，我们大致会得到以下的设计方案。</p>
<pre><code>           +---------+   +-----------+   +----------+
request --&gt;|  parse  |--&gt;|  combine  |--&gt;|  output  |--&gt; response
           +---------+   +-----------+   +----------+
</code></pre><p>也就是说，服务器会首先分析URL，得到请求的文件的路径和类型（MIME）。然后，服务器会读取请求的文件，并按顺序合并文件内容。最后，服务器返回响应，完成对一次请求的处理。</p>
<p>另外，服务器在读取文件时需要有个根目录，并且服务器监听的HTTP端口最好也不要写死在代码里，因此服务器需要是可配置的。</p>
<h4 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h4><p>根据以上设计，我们写出了第一版代码如下。</p>
<pre><code>var fs = require(&apos;fs&apos;),
    path = require(&apos;path&apos;),
    http = require(&apos;http&apos;);

var MIME = {
    &apos;.css&apos;: &apos;text/css&apos;,
    &apos;.js&apos;: &apos;application/javascript&apos;
};

function combineFiles(pathnames, callback) {
    var output = [];

    (function next(i, len) {
        if (i &lt; len) {
            fs.readFile(pathnames[i], function (err, data) {
                if (err) {
                    callback(err);
                } else {
                    output.push(data);
                    next(i + 1, len);
                }
            });
        } else {
            callback(null, Buffer.concat(output));
        }
    }(0, pathnames.length));
}

function main(argv) {
    var config = JSON.parse(fs.readFileSync(argv[0], &apos;utf-8&apos;)),
        root = config.root || &apos;.&apos;,
        port = config.port || 80;

    http.createServer(function (request, response) {
        var urlInfo = parseURL(root, request.url);

        combineFiles(urlInfo.pathnames, function (err, data) {
            if (err) {
                response.writeHead(404);
                response.end(err.message);
            } else {
                response.writeHead(200, {
                    &apos;Content-Type&apos;: urlInfo.mime
                });
                response.end(data);
            }
        });
    }).listen(port);
}

function parseURL(root, url) {
    var base, pathnames, parts;

    if (url.indexOf(&apos;??&apos;) === -1) {
        url = url.replace(&apos;/&apos;, &apos;/??&apos;);
    }

    parts = url.split(&apos;??&apos;);
    base = parts[0];
    pathnames = parts[1].split(&apos;,&apos;).map(function (value) {
        return path.join(root, base, value);
    });

    return {
        mime: MIME[path.extname(pathnames[0])] || &apos;text/plain&apos;,
        pathnames: pathnames
    };
}

main(process.argv.slice(2));
</code></pre><p>以上代码完整实现了服务器所需的功能，并且有以下几点值得注意：</p>
<ol>
<li><p>使用命令行参数传递JSON配置文件路径，入口函数负责读取配置并创建服务器。</p>
</li>
<li><p>入口函数完整描述了程序的运行逻辑，其中解析URL和合并文件的具体实现封装在其它两个函数里。</p>
</li>
<li><p>解析URL时先将普通URL转换为了文件合并URL，使得两种URL的处理方式可以一致。</p>
</li>
<li><p>合并文件时使用异步API读取文件，避免服务器因等待磁盘IO而发生阻塞。</p>
</li>
</ol>
<p>我们可以把以上代码保存为<code>server.js</code>，之后就可以通过<code>node server.js config.json</code>命令启动程序，于是我们的第一版静态文件合并服务器就顺利完工了。</p>
<p>另外，以上代码存在一个不那么明显的逻辑缺陷。例如，使用以下URL请求服务器时会有惊喜。</p>
<pre><code>http://assets.example.com/foo/bar.js,foo/baz.js
</code></pre><p>经过分析之后我们会发现问题出在<code>/</code>被自动替换<code>/??</code>这个行为上，而这个问题我们可以到第二次迭代时再解决。</p>
<h3 id="第二次迭代"><a href="#第二次迭代" class="headerlink" title="第二次迭代"></a>第二次迭代</h3><p>在第一次迭代之后，我们已经有了一个可工作的版本，满足了功能需求。接下来我们需要从性能的角度出发，看看代码还有哪些改进余地。</p>
<h4 id="设计-1"><a href="#设计-1" class="headerlink" title="设计"></a>设计</h4><p>把<code>map</code>方法换成<code>for</code>循环或许会更快一些，但第一版代码最大的性能问题存在于从读取文件到输出响应的过程当中。我们以处理<code>/??a.js,b.js,c.js</code>这个请求为例，看看整个处理过程中耗时在哪儿。</p>
<pre><code> 发送请求       等待服务端响应         接收响应
---------+----------------------+-------------&gt;
         --                                        解析请求
           ------                                  读取a.js
                 ------                            读取b.js
                       ------                      读取c.js
                             --                    合并数据
                               --                  输出响应
</code></pre><p>可以看到，第一版代码依次把请求的文件读取到内存中之后，再合并数据和输出响应。这会导致以下两个问题：</p>
<ol>
<li><p>当请求的文件比较多比较大时，串行读取文件会比较耗时，从而拉长了服务端响应等待时间。</p>
</li>
<li><p>由于每次响应输出的数据都需要先完整地缓存在内存里，当服务器请求并发数较大时，会有较大的内存开销。</p>
</li>
</ol>
<p>对于第一个问题，很容易想到把读取文件的方式从串行改为并行。但是别这样做，因为对于机械磁盘而言，因为只有一个磁头，尝试并行读取文件只会造成磁头频繁抖动，反而降低IO效率。而对于固态硬盘，虽然的确存在多个并行IO通道，但是对于服务器并行处理的多个请求而言，硬盘已经在做并行IO了，对单个请求采用并行IO无异于拆东墙补西墙。因此，正确的做法不是改用并行IO，而是一边读取文件一边输出响应，把响应输出时机提前至读取第一个文件的时刻。这样调整后，整个请求处理过程变成下边这样。</p>
<pre><code>发送请求 等待服务端响应 接收响应
---------+----+-------------------------------&gt;
         --                                        解析请求
           --                                      检查文件是否存在
             --                                    输出响应头
               ------                              读取和输出a.js
                     ------                        读取和输出b.js
                           ------                  读取和输出c.js
</code></pre><p>按上述方式解决第一个问题后，因为服务器不需要完整地缓存每个请求的输出数据了，第二个问题也迎刃而解。</p>
<h4 id="实现-1"><a href="#实现-1" class="headerlink" title="实现"></a>实现</h4><p>根据以上设计，第二版代码按以下方式调整了部分函数。</p>
<pre><code>function main(argv) {
    var config = JSON.parse(fs.readFileSync(argv[0], &apos;utf-8&apos;)),
        root = config.root || &apos;.&apos;,
        port = config.port || 80;

    http.createServer(function (request, response) {
        var urlInfo = parseURL(root, request.url);

        validateFiles(urlInfo.pathnames, function (err, pathnames) {
            if (err) {
                response.writeHead(404);
                response.end(err.message);
            } else {
                response.writeHead(200, {
                    &apos;Content-Type&apos;: urlInfo.mime
                });
                outputFiles(pathnames, response);
            }
        });
    }).listen(port);
}

function outputFiles(pathnames, writer) {
    (function next(i, len) {
        if (i &lt; len) {
            var reader = fs.createReadStream(pathnames[i]);

            reader.pipe(writer, { end: false });
            reader.on(&apos;end&apos;, function() {
                next(i + 1, len);
            });
        } else {
            writer.end();
        }
    }(0, pathnames.length));
}

function validateFiles(pathnames, callback) {
    (function next(i, len) {
        if (i &lt; len) {
            fs.stat(pathnames[i], function (err, stats) {
                if (err) {
                    callback(err);
                } else if (!stats.isFile()) {
                    callback(new Error());
                } else {
                    next(i + 1, len);
                }
            });
        } else {
            callback(null, pathnames);
        }
    }(0, pathnames.length));
}
</code></pre><p>可以看到，第二版代码在检查了请求的所有文件是否有效之后，立即就输出了响应头，并接着一边按顺序读取文件一边输出响应内容。并且，在读取文件时，第二版代码直接使用了只读数据流来简化代码。</p>
<h3 id="第三次迭代"><a href="#第三次迭代" class="headerlink" title="第三次迭代"></a>第三次迭代</h3><p>第二次迭代之后，服务器本身的功能和性能已经得到了初步满足。接下来我们需要从稳定性的角度重新审视一下代码，看看还需要做些什么。</p>
<h4 id="设计-2"><a href="#设计-2" class="headerlink" title="设计"></a>设计</h4><p>从工程角度上讲，没有绝对可靠的系统。即使第二次迭代的代码经过反复检查后能确保没有bug，也很难说是否会因为NodeJS本身，或者是操作系统本身，甚至是硬件本身导致我们的服务器程序在某一天挂掉。因此一般生产环境下的服务器程序都配有一个守护进程，在服务挂掉的时候立即重启服务。一般守护进程的代码会远比服务进程的代码简单，从概率上可以保证守护进程更难挂掉。如果再做得严谨一些，甚至守护进程自身可以在自己挂掉时重启自己，从而实现双保险。</p>
<p>因此在本次迭代时，我们先利用NodeJS的进程管理机制，将守护进程作为父进程，将服务器程序作为子进程，并让父进程监控子进程的运行状态，在其异常退出时重启子进程。</p>
<h4 id="实现-2"><a href="#实现-2" class="headerlink" title="实现"></a>实现</h4><p>根据以上设计，我们编写了守护进程需要的代码。</p>
<pre><code>var cp = require(&apos;child_process&apos;);

var worker;

function spawn(server, config) {
    worker = cp.spawn(&apos;node&apos;, [ server, config ]);
    worker.on(&apos;exit&apos;, function (code) {
        if (code !== 0) {
            spawn(server, config);
        }
    });
}

function main(argv) {
    spawn(&apos;server.js&apos;, argv[0]);
    process.on(&apos;SIGTERM&apos;, function () {
        worker.kill();
        process.exit(0);
    });
}

main(process.argv.slice(2));
</code></pre><p>此外，服务器代码本身的入口函数也要做以下调整。</p>
<pre><code>function main(argv) {
    var config = JSON.parse(fs.readFileSync(argv[0], &apos;utf-8&apos;)),
        root = config.root || &apos;.&apos;,
        port = config.port || 80,
        server;

    server = http.createServer(function (request, response) {
        ...
    }).listen(port);

    process.on(&apos;SIGTERM&apos;, function () {
        server.close(function () {
            process.exit(0);
        });
    });
}
</code></pre><p>我们可以把守护进程的代码保存为<code>daemon.js</code>，之后我们可以通过<code>node daemon.js config.json</code>启动服务，而守护进程会进一步启动和监控服务器进程。此外，为了能够正常终止服务，我们让守护进程在接收到<code>SIGTERM</code>信号时终止服务器进程。而在服务器进程这一端，同样在收到<code>SIGTERM</code>信号时先停掉HTTP服务再正常退出。至此，我们的服务器程序就靠谱很多了。</p>
<h3 id="第四次迭代"><a href="#第四次迭代" class="headerlink" title="第四次迭代"></a>第四次迭代</h3><p>在我们解决了服务器本身的功能、性能和可靠性的问题后，接着我们需要考虑一下代码部署的问题，以及服务器控制的问题。</p>
<h4 id="设计-3"><a href="#设计-3" class="headerlink" title="设计"></a>设计</h4><p>一般而言，程序在服务器上有一个固定的部署目录，每次程序有更新后，都重新发布到部署目录里。而一旦完成部署后，一般也可以通过固定的服务控制脚本启动和停止服务。因此我们的服务器程序部署目录可以做如下设计。</p>
<pre><code>- deploy/
    - bin/
        startws.sh
        killws.sh
    + conf/
        config.json
    + lib/
        daemon.js
        server.js
</code></pre><p>在以上目录结构中，我们分类存放了服务控制脚本、配置文件和服务器代码。</p>
<h4 id="实现-3"><a href="#实现-3" class="headerlink" title="实现"></a>实现</h4><p>按以上目录结构分别存放对应的文件之后，接下来我们看看控制脚本怎么写。首先是<code>start.sh</code>。</p>
<pre><code>#!/bin/sh
if [ ! -f &quot;pid&quot; ]
then
    node ../lib/daemon.js ../conf/config.json &amp;
    echo $! &gt; pid
fi
</code></pre><p>然后是<code>killws.sh</code>。</p>
<pre><code>#!/bin/sh
if [ -f &quot;pid&quot; ]
then
    kill $(tr -d &apos;\r\n&apos; &lt; pid)
    rm pid
fi
</code></pre><p>于是这样我们就有了一个简单的代码部署目录和服务控制脚本，我们的服务器程序就可以上线工作了。</p>
<h3 id="后续迭代"><a href="#后续迭代" class="headerlink" title="后续迭代"></a>后续迭代</h3><p>我们的服务器程序正式上线工作后，我们接下来或许会发现还有很多可以改进的点。比如服务器程序在合并JS文件时可以自动在JS文件之间插入一个<code>;</code>来避免一些语法问题，比如服务器程序需要提供日志来统计访问量，比如服务器程序需要能充分利用多核CPU，等等。而此时的你，在学习了这么久NodeJS之后，应该已经知道该怎么做了。</p>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>本章将之前零散介绍的知识点串了起来，完整地演示了一个使用NodeJS开发程序的例子，至此我们的课程就全部结束了。以下是对新诞生的NodeJSer的一些建议。</p>
<ul>
<li><p>要熟悉官方API文档。并不是说要熟悉到能记住每个API的名称和用法，而是要熟悉NodeJS提供了哪些功能，一旦需要时知道查询API文档的哪块地方。</p>
</li>
<li><p>要先设计再实现。在开发一个程序前首先要有一个全局的设计，不一定要很周全，但要足够能写出一些代码。</p>
</li>
<li><p>要实现后再设计。在写了一些代码，有了一些具体的东西后，一定会发现一些之前忽略掉的细节。这时再反过来改进之前的设计，为第二轮迭代做准备。</p>
</li>
<li><p>要充分利用三方包。NodeJS有一个庞大的生态圈，在写代码之前先看看有没有现成的三方包能节省不少时间。</p>
</li>
<li><p>不要迷信三方包。任何事情做过头了就不好了，三方包也是一样。三方包是一个黑盒，每多使用一个三方包，就为程序增加了一份潜在风险。并且三方包很难恰好只提供程序需要的功能，每多使用一个三方包，就让程序更加臃肿一些。因此在决定使用某个三方包之前，最好三思而后行。</p>
</li>
</ul>

        
        </div>
        <footer class="article-footer">
            <div class="share-container">



</div>

    <a data-url="http://yoursite.com/2016/03/23/node/books/7days/07_example/" data-id="cj6oqpm1v002uyjpjzma190xb" class="article-share-link"><i class="fa fa-share"></i>Share</a>
<script>
    (function ($) {
        // Prevent duplicate binding
        if (typeof(__SHARE_BUTTON_BINDED__) === 'undefined' || !__SHARE_BUTTON_BINDED__) {
            __SHARE_BUTTON_BINDED__ = true;
        } else {
            return;
        }
        $('body').on('click', function() {
            $('.article-share-box.on').removeClass('on');
        }).on('click', '.article-share-link', function(e) {
            e.stopPropagation();

            var $this = $(this),
                url = $this.attr('data-url'),
                encodedUrl = encodeURIComponent(url),
                id = 'article-share-box-' + $this.attr('data-id'),
                offset = $this.offset(),
                box;

            if ($('#' + id).length) {
                box = $('#' + id);

                if (box.hasClass('on')){
                    box.removeClass('on');
                    return;
                }
            } else {
                var html = [
                    '<div id="' + id + '" class="article-share-box">',
                        '<input class="article-share-input" value="' + url + '">',
                        '<div class="article-share-links">',
                            '<a href="https://twitter.com/intent/tweet?url=' + encodedUrl + '" class="fa fa-twitter article-share-twitter" target="_blank" title="Twitter"></a>',
                            '<a href="https://www.facebook.com/sharer.php?u=' + encodedUrl + '" class="fa fa-facebook article-share-facebook" target="_blank" title="Facebook"></a>',
                            '<a href="http://pinterest.com/pin/create/button/?url=' + encodedUrl + '" class="fa fa-pinterest article-share-pinterest" target="_blank" title="Pinterest"></a>',
                            '<a href="https://plus.google.com/share?url=' + encodedUrl + '" class="fa fa-google article-share-google" target="_blank" title="Google+"></a>',
                        '</div>',
                    '</div>'
                ].join('');

              box = $(html);

              $('body').append(box);
            }

            $('.article-share-box.on').hide();

            box.css({
                top: offset.top + 25,
                left: offset.left
            }).addClass('on');

        }).on('click', '.article-share-box', function (e) {
            e.stopPropagation();
        }).on('click', '.article-share-box-input', function () {
            $(this).select();
        }).on('click', '.article-share-box-link', function (e) {
            e.preventDefault();
            e.stopPropagation();

            window.open(this.href, 'article-share-box-window-' + Date.now(), 'width=500,height=450');
        });
    })(jQuery);
</script>

            
    
        <a href="http://yoursite.com/2016/03/23/node/books/7days/07_example/#comments" class="article-comment-link disqus-comment-count" data-disqus-url="http://yoursite.com/2016/03/23/node/books/7days/07_example/">Comments</a>
    

        </footer>
    </div>
    
</article>



    <article id="post-node/books/7days/03_file" class="article article-type-post" itemscope itemprop="blogPost">
    <div class="article-inner">
        
        
        
        <div class="article-entry" itemprop="articleBody">
        
            
            <h2 id="七天学会NodeJS-文件操作"><a href="#七天学会NodeJS-文件操作" class="headerlink" title="七天学会NodeJS - 文件操作"></a>七天学会NodeJS - 文件操作</h2><p>让前端觉得如获神器的不是NodeJS能做网络编程，而是NodeJS能够操作文件。小至文件查找，大至代码编译，几乎没有一个前端工具不操作文件。换个角度讲，几乎也只需要一些数据处理逻辑，再加上一些文件操作，就能够编写出大多数前端工具。本章将介绍与之相关的NodeJS内置模块。</p>
<h3 id="开门红"><a href="#开门红" class="headerlink" title="开门红"></a>开门红</h3><p>NodeJS提供了基本的文件操作API，但是像文件拷贝这种高级功能就没有提供，因此我们先拿文件拷贝程序练手。与<code>copy</code>命令类似，我们的程序需要能接受源文件路径与目标文件路径两个参数。</p>
<h4 id="小文件拷贝"><a href="#小文件拷贝" class="headerlink" title="小文件拷贝"></a>小文件拷贝</h4><p>我们使用NodeJS内置的<code>fs</code>模块简单实现这个程序如下。</p>
<pre><code>var fs = require(&apos;fs&apos;);

function copy(src, dst) {
    fs.writeFileSync(dst, fs.readFileSync(src));
}

function main(argv) {
    copy(argv[0], argv[1]);
}

main(process.argv.slice(2));
</code></pre><p>以上程序使用<code>fs.readFileSync</code>从源路径读取文件内容，并使用<code>fs.writeFileSync</code>将文件内容写入目标路径。</p>
<blockquote>
<p>   <strong>豆知识：</strong> <code>process</code>是一个全局变量，可通过<code>process.argv</code>获得命令行参数。由于<code>argv[0]</code>固定等于NodeJS执行程序的绝对路径，<code>argv[1]</code>固定等于主模块的绝对路径，因此第一个命令行参数从<code>argv[2]</code>这个位置开始。</p>
</blockquote>
<h4 id="大文件拷贝"><a href="#大文件拷贝" class="headerlink" title="大文件拷贝"></a>大文件拷贝</h4><p>上边的程序拷贝一些小文件没啥问题，但这种一次性把所有文件内容都读取到内存中后再一次性写入磁盘的方式不适合拷贝大文件，内存会爆仓。对于大文件，我们只能读一点写一点，直到完成拷贝。因此上边的程序需要改造如下。</p>
<pre><code>var fs = require(&apos;fs&apos;);

function copy(src, dst) {
    fs.createReadStream(src).pipe(fs.createWriteStream(dst));
}

function main(argv) {
    copy(argv[0], argv[1]);
}

main(process.argv.slice(2));
</code></pre><p>以上程序使用<code>fs.createReadStream</code>创建了一个源文件的只读数据流，并使用<code>fs.createWriteStream</code>创建了一个目标文件的只写数据流，并且用<code>pipe</code>方法把两个数据流连接了起来。连接起来后发生的事情，说得抽象点的话，水顺着水管从一个桶流到了另一个桶。</p>
<h3 id="API走马观花"><a href="#API走马观花" class="headerlink" title="API走马观花"></a>API走马观花</h3><p>我们先大致看看NodeJS提供了哪些和文件操作有关的API。这里并不逐一介绍每个API的使用方法，官方文档已经做得很好了。</p>
<h4 id="Buffer（数据块）"><a href="#Buffer（数据块）" class="headerlink" title="Buffer（数据块）"></a>Buffer（数据块）</h4><blockquote>
<p>   <strong>官方文档： </strong> <a href="http://nodejs.org/api/buffer.html" target="_blank" rel="external">http://nodejs.org/api/buffer.html</a></p>
</blockquote>
<p>JS语言自身只有字符串数据类型，没有二进制数据类型，因此NodeJS提供了一个与<code>String</code>对等的全局构造函数<code>Buffer</code>来提供对二进制数据的操作。除了可以读取文件得到<code>Buffer</code>的实例外，还能够直接构造，例如：</p>
<pre><code>var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);
</code></pre><p><code>Buffer</code>与字符串类似，除了可以用<code>.length</code>属性得到字节长度外，还可以用<code>[index]</code>方式读取指定位置的字节，例如：</p>
<pre><code>bin[0]; // =&gt; 0x68;
</code></pre><p><code>Buffer</code>与字符串能够互相转化，例如可以使用指定编码将二进制数据转化为字符串：</p>
<pre><code>var str = bin.toString(&apos;utf-8&apos;); // =&gt; &quot;hello&quot;
</code></pre><p>或者反过来，将字符串转换为指定编码下的二进制数据：</p>
<pre><code>var bin = new Buffer(&apos;hello&apos;, &apos;utf-8&apos;); // =&gt; &lt;Buffer 68 65 6c 6c 6f&gt;
</code></pre><p><code>Buffer</code>与字符串有一个重要区别。字符串是只读的，并且对字符串的任何修改得到的都是一个新字符串，原字符串保持不变。至于<code>Buffer</code>，更像是可以做指针操作的C语言数组。例如，可以用<code>[index]</code>方式直接修改某个位置的字节。</p>
<pre><code>bin[0] = 0x48;
</code></pre><p>而<code>.slice</code>方法也不是返回一个新的<code>Buffer</code>，而更像是返回了指向原<code>Buffer</code>中间的某个位置的指针，如下所示。</p>
<pre><code>[ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]
    ^           ^
    |           |
   bin     bin.slice(2)
</code></pre><p>因此对<code>.slice</code>方法返回的<code>Buffer</code>的修改会作用于原<code>Buffer</code>，例如：</p>
<pre><code>var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);
var sub = bin.slice(2);

sub[0] = 0x65;
console.log(bin); // =&gt; &lt;Buffer 68 65 65 6c 6f&gt;
</code></pre><p>也因此，如果想要拷贝一份<code>Buffer</code>，得首先创建一个新的<code>Buffer</code>，并通过<code>.copy</code>方法把原<code>Buffer</code>中的数据复制过去。这个类似于申请一块新的内存，并把已有内存中的数据复制过去。以下是一个例子。</p>
<pre><code>var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);
var dup = new Buffer(bin.length);

bin.copy(dup);
dup[0] = 0x48;
console.log(bin); // =&gt; &lt;Buffer 68 65 6c 6c 6f&gt;
console.log(dup); // =&gt; &lt;Buffer 48 65 65 6c 6f&gt;
</code></pre><p>总之，<code>Buffer</code>将JS的数据处理能力从字符串扩展到了任意二进制数据。</p>
<h4 id="Stream（数据流）"><a href="#Stream（数据流）" class="headerlink" title="Stream（数据流）"></a>Stream（数据流）</h4><blockquote>
<p>   <strong>官方文档： </strong> <a href="http://nodejs.org/api/stream.html" target="_blank" rel="external">http://nodejs.org/api/stream.html</a></p>
</blockquote>
<p>当内存中无法一次装下需要处理的数据时，或者一边读取一边处理更加高效时，我们就需要用到数据流。NodeJS中通过各种<code>Stream</code>来提供对数据流的操作。</p>
<p>以上边的大文件拷贝程序为例，我们可以为数据来源创建一个只读数据流，示例如下：</p>
<pre><code>var rs = fs.createReadStream(pathname);

rs.on(&apos;data&apos;, function (chunk) {
    doSomething(chunk);
});

rs.on(&apos;end&apos;, function () {
    cleanUp();
});
</code></pre><blockquote>
<p>   <strong>豆知识：</strong> <code>Stream</code>基于事件机制工作，所有<code>Stream</code>的实例都继承于NodeJS提供的<a href="http://nodejs.org/api/events.html" target="_blank" rel="external">EventEmitter</a>。</p>
</blockquote>
<p>上边的代码中<code>data</code>事件会源源不断地被触发，不管<code>doSomething</code>函数是否处理得过来。代码可以继续做如下改造，以解决这个问题。</p>
<pre><code>var rs = fs.createReadStream(src);

rs.on(&apos;data&apos;, function (chunk) {
    rs.pause();
    doSomething(chunk, function () {
        rs.resume();
    });
});

rs.on(&apos;end&apos;, function () {
    cleanUp();
});
</code></pre><p>以上代码给<code>doSomething</code>函数加上了回调，因此我们可以在处理数据前暂停数据读取，并在处理数据后继续读取数据。</p>
<p>此外，我们也可以为数据目标创建一个只写数据流，示例如下：</p>
<pre><code>var rs = fs.createReadStream(src);
var ws = fs.createWriteStream(dst);

rs.on(&apos;data&apos;, function (chunk) {
    ws.write(chunk);
});

rs.on(&apos;end&apos;, function () {
    ws.end();
});
</code></pre><p>我们把<code>doSomething</code>换成了往只写数据流里写入数据后，以上代码看起来就像是一个文件拷贝程序了。但是以上代码存在上边提到的问题，如果写入速度跟不上读取速度的话，只写数据流内部的缓存会爆仓。我们可以根据<code>.write</code>方法的返回值来判断传入的数据是写入目标了，还是临时放在了缓存了，并根据<code>drain</code>事件来判断什么时候只写数据流已经将缓存中的数据写入目标，可以传入下一个待写数据了。因此代码可以改造如下：</p>
<pre><code>var rs = fs.createReadStream(src);
var ws = fs.createWriteStream(dst);

rs.on(&apos;data&apos;, function (chunk) {
    if (ws.write(chunk) === false) {
        rs.pause();
    }
});

rs.on(&apos;end&apos;, function () {
    ws.end();
});

ws.on(&apos;drain&apos;, function () {
    rs.resume();
});
</code></pre><p>以上代码实现了数据从只读数据流到只写数据流的搬运，并包括了防爆仓控制。因为这种使用场景很多，例如上边的大文件拷贝程序，NodeJS直接提供了<code>.pipe</code>方法来做这件事情，其内部实现方式与上边的代码类似。</p>
<h4 id="File-System（文件系统）"><a href="#File-System（文件系统）" class="headerlink" title="File System（文件系统）"></a>File System（文件系统）</h4><blockquote>
<p>   <strong>官方文档： </strong> <a href="http://nodejs.org/api/fs.html" target="_blank" rel="external">http://nodejs.org/api/fs.html</a></p>
</blockquote>
<p>NodeJS通过<code>fs</code>内置模块提供对文件的操作。<code>fs</code>模块提供的API基本上可以分为以下三类：</p>
<ul>
<li><p>文件属性读写。</p>
<p>  其中常用的有<code>fs.stat</code>、<code>fs.chmod</code>、<code>fs.chown</code>等等。</p>
</li>
<li><p>文件内容读写。</p>
<p>  其中常用的有<code>fs.readFile</code>、<code>fs.readdir</code>、<code>fs.writeFile</code>、<code>fs.mkdir</code>等等。</p>
</li>
<li><p>底层文件操作。</p>
<p>  其中常用的有<code>fs.open</code>、<code>fs.read</code>、<code>fs.write</code>、<code>fs.close</code>等等。</p>
</li>
</ul>
<p>NodeJS最精华的异步IO模型在<code>fs</code>模块里有着充分的体现，例如上边提到的这些API都通过回调函数传递结果。以<code>fs.readFile</code>为例：</p>
<pre><code>fs.readFile(pathname, function (err, data) {
    if (err) {
        // Deal with error.
    } else {
        // Deal with data.
    }
});
</code></pre><p>如上边代码所示，基本上所有<code>fs</code>模块API的回调参数都有两个。第一个参数在有错误发生时等于异常对象，第二个参数始终用于返回API方法执行结果。</p>
<p>此外，<code>fs</code>模块的所有异步API都有对应的同步版本，用于无法使用异步操作时，或者同步操作更方便时的情况。同步API除了方法名的末尾多了一个<code>Sync</code>之外，异常对象与执行结果的传递方式也有相应变化。同样以<code>fs.readFileSync</code>为例：</p>
<pre><code>try {
    var data = fs.readFileSync(pathname);
    // Deal with data.
} catch (err) {
    // Deal with error.
}
</code></pre><p><code>fs</code>模块提供的API很多，这里不一一介绍，需要时请自行查阅官方文档。</p>
<h4 id="Path（路径）"><a href="#Path（路径）" class="headerlink" title="Path（路径）"></a>Path（路径）</h4><blockquote>
<p>   <strong>官方文档： </strong> <a href="http://nodejs.org/api/path.html" target="_blank" rel="external">http://nodejs.org/api/path.html</a></p>
</blockquote>
<p>操作文件时难免不与文件路径打交道。NodeJS提供了<code>path</code>内置模块来简化路径相关操作，并提升代码可读性。以下分别介绍几个常用的API。</p>
<ul>
<li><p>path.normalize</p>
<p>  将传入的路径转换为标准路径，具体讲的话，除了解析路径中的<code>.</code>与<code>..</code>外，还能去掉多余的斜杠。如果有程序需要使用路径作为某些数据的索引，但又允许用户随意输入路径时，就需要使用该方法保证路径的唯一性。以下是一个例子：</p>
<pre><code>var cache = {};

function store(key, value) {
    cache[path.normalize(key)] = value;
}

store(&apos;foo/bar&apos;, 1);
store(&apos;foo//baz//../bar&apos;, 2);
console.log(cache);  // =&gt; { &quot;foo/bar&quot;: 2 }
</code></pre><blockquote>
<p>   <strong>坑出没注意： </strong> 标准化之后的路径里的斜杠在Windows系统下是<code>\</code>，而在Linux系统下是<code>/</code>。如果想保证任何系统下都使用<code>/</code>作为路径分隔符的话，需要用<code>.replace(/\\/g, &#39;/&#39;)</code>再替换一下标准路径。</p>
</blockquote>
</li>
<li><p>path.join</p>
<p>  将传入的多个路径拼接为标准路径。该方法可避免手工拼接路径字符串的繁琐，并且能在不同系统下正确使用相应的路径分隔符。以下是一个例子：</p>
<pre><code>path.join(&apos;foo/&apos;, &apos;baz/&apos;, &apos;../bar&apos;); // =&gt; &quot;foo/bar&quot;
</code></pre></li>
<li><p>path.extname</p>
<p>  当我们需要根据不同文件扩展名做不同操作时，该方法就显得很好用。以下是一个例子：</p>
<pre><code>path.extname(&apos;foo/bar.js&apos;); // =&gt; &quot;.js&quot;
</code></pre></li>
</ul>
<p><code>path</code>模块提供的其余方法也不多，稍微看一下官方文档就能全部掌握。</p>
<h3 id="遍历目录"><a href="#遍历目录" class="headerlink" title="遍历目录"></a>遍历目录</h3><p>遍历目录是操作文件时的一个常见需求。比如写一个程序，需要找到并处理指定目录下的所有JS文件时，就需要遍历整个目录。</p>
<h4 id="递归算法"><a href="#递归算法" class="headerlink" title="递归算法"></a>递归算法</h4><p>遍历目录时一般使用递归算法，否则就难以编写出简洁的代码。递归算法与数学归纳法类似，通过不断缩小问题的规模来解决问题。以下示例说明了这种方法。</p>
<pre><code>function factorial(n) {
    if (n === 1) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}
</code></pre><p>上边的函数用于计算N的阶乘（N!）。可以看到，当N大于1时，问题简化为计算N乘以N-1的阶乘。当N等于1时，问题达到最小规模，不需要再简化，因此直接返回1。</p>
<blockquote>
<p>   <strong>陷阱：</strong> 使用递归算法编写的代码虽然简洁，但由于每递归一次就产生一次函数调用，在需要优先考虑性能时，需要把递归算法转换为循环算法，以减少函数调用次数。</p>
</blockquote>
<h4 id="遍历算法"><a href="#遍历算法" class="headerlink" title="遍历算法"></a>遍历算法</h4><p>目录是一个树状结构，在遍历时一般使用深度优先+先序遍历算法。深度优先，意味着到达一个节点后，首先接着遍历子节点而不是邻居节点。先序遍历，意味着首次到达了某节点就算遍历完成，而不是最后一次返回某节点才算数。因此使用这种遍历方式时，下边这棵树的遍历顺序是<code>A &gt; B &gt; D &gt; E &gt; C &gt; F</code>。</p>
<pre><code>    A
   / \
  B   C
 / \   \
D   E   F
</code></pre><h4 id="同步遍历"><a href="#同步遍历" class="headerlink" title="同步遍历"></a>同步遍历</h4><p>了解了必要的算法后，我们可以简单地实现以下目录遍历函数。</p>
<pre><code>function travel(dir, callback) {
    fs.readdirSync(dir).forEach(function (file) {
        var pathname = path.join(dir, file);

        if (fs.statSync(pathname).isDirectory()) {
            travel(pathname, callback);
        } else {
            callback(pathname);
        }
    });
}
</code></pre><p>可以看到，该函数以某个目录作为遍历的起点。遇到一个子目录时，就先接着遍历子目录。遇到一个文件时，就把文件的绝对路径传给回调函数。回调函数拿到文件路径后，就可以做各种判断和处理。因此假设有以下目录：</p>
<pre><code>- /home/user/
    - foo/
        x.js
    - bar/
        y.js
    z.css
</code></pre><p>使用以下代码遍历该目录时，得到的输入如下。</p>
<pre><code>travel(&apos;/home/user&apos;, function (pathname) {
    console.log(pathname);
});

------------------------
/home/user/foo/x.js
/home/user/bar/y.js
/home/user/z.css
</code></pre><h4 id="异步遍历"><a href="#异步遍历" class="headerlink" title="异步遍历"></a>异步遍历</h4><p>如果读取目录或读取文件状态时使用的是异步API，目录遍历函数实现起来会有些复杂，但原理完全相同。<code>travel</code>函数的异步版本如下。</p>
<pre><code>function travel(dir, callback, finish) {
    fs.readdir(dir, function (err, files) {
        (function next(i) {
            if (i &lt; files.length) {
                var pathname = path.join(dir, files[i]);

                fs.stat(pathname, function (err, stats) {
                    if (stats.isDirectory()) {
                        travel(pathname, callback, function () {
                            next(i + 1);
                        });
                    } else {
                        callback(pathname, function () {
                            next(i + 1);
                        });
                    }
                });
            } else {
                finish &amp;&amp; finish();
            }
        }(0));
    });
}
</code></pre><p>这里不详细介绍异步遍历函数的编写技巧，在后续章节中会详细介绍这个。总之我们可以看到异步编程还是蛮复杂的。</p>
<h3 id="文本编码"><a href="#文本编码" class="headerlink" title="文本编码"></a>文本编码</h3><p>使用NodeJS编写前端工具时，操作得最多的是文本文件，因此也就涉及到了文件编码的处理问题。我们常用的文本编码有<code>UTF8</code>和<code>GBK</code>两种，并且<code>UTF8</code>文件还可能带有BOM。在读取不同编码的文本文件时，需要将文件内容转换为JS使用的<code>UTF8</code>编码字符串后才能正常处理。</p>
<h4 id="BOM的移除"><a href="#BOM的移除" class="headerlink" title="BOM的移除"></a>BOM的移除</h4><p>BOM用于标记一个文本文件使用Unicode编码，其本身是一个Unicode字符（”\uFEFF”），位于文本文件头部。在不同的Unicode编码下，BOM字符对应的二进制字节如下：</p>
<pre><code>    Bytes      Encoding
----------------------------
    FE FF       UTF16BE
    FF FE       UTF16LE
    EF BB BF    UTF8
</code></pre><p>因此，我们可以根据文本文件头几个字节等于啥来判断文件是否包含BOM，以及使用哪种Unicode编码。但是，BOM字符虽然起到了标记文件编码的作用，其本身却不属于文件内容的一部分，如果读取文本文件时不去掉BOM，在某些使用场景下就会有问题。例如我们把几个JS文件合并成一个文件后，如果文件中间含有BOM字符，就会导致浏览器JS语法错误。因此，使用NodeJS读取文本文件时，一般需要去掉BOM。例如，以下代码实现了识别和去除UTF8 BOM的功能。</p>
<pre><code>function readText(pathname) {
    var bin = fs.readFileSync(pathname);

    if (bin[0] === 0xEF &amp;&amp; bin[1] === 0xBB &amp;&amp; bin[2] === 0xBF) {
        bin = bin.slice(3);
    }

    return bin.toString(&apos;utf-8&apos;);
}
</code></pre><h4 id="GBK转UTF8"><a href="#GBK转UTF8" class="headerlink" title="GBK转UTF8"></a>GBK转UTF8</h4><p>NodeJS支持在读取文本文件时，或者在<code>Buffer</code>转换为字符串时指定文本编码，但遗憾的是，GBK编码不在NodeJS自身支持范围内。因此，一般我们借助<code>iconv-lite</code>这个三方包来转换编码。使用NPM下载该包后，我们可以按下边方式编写一个读取GBK文本文件的函数。</p>
<pre><code>var iconv = require(&apos;iconv-lite&apos;);

function readGBKText(pathname) {
    var bin = fs.readFileSync(pathname);

    return iconv.decode(bin, &apos;gbk&apos;);
}
</code></pre><h4 id="单字节编码"><a href="#单字节编码" class="headerlink" title="单字节编码"></a>单字节编码</h4><p>有时候，我们无法预知需要读取的文件采用哪种编码，因此也就无法指定正确的编码。比如我们要处理的某些CSS文件中，有的用GBK编码，有的用UTF8编码。虽然可以一定程度可以根据文件的字节内容猜测出文本编码，但这里要介绍的是有些局限，但是要简单得多的一种技术。</p>
<p>首先我们知道，如果一个文本文件只包含英文字符，比如<code>Hello World</code>，那无论用GBK编码或是UTF8编码读取这个文件都是没问题的。这是因为在这些编码下，ASCII0~128范围内字符都使用相同的单字节编码。</p>
<p>反过来讲，即使一个文本文件中有中文等字符，如果我们需要处理的字符仅在ASCII0~128范围内，比如除了注释和字符串以外的JS代码，我们就可以统一使用单字节编码来读取文件，不用关心文件的实际编码是GBK还是UTF8。以下示例说明了这种方法。</p>
<pre><code>1. GBK编码源文件内容：
    var foo = &apos;中文&apos;;
2. 对应字节：
    76 61 72 20 66 6F 6F 20 3D 20 27 D6 D0 CE C4 27 3B
3. 使用单字节编码读取后得到的内容：
    var foo = &apos;{乱码}{乱码}{乱码}{乱码}&apos;;
4. 替换内容：
    var bar = &apos;{乱码}{乱码}{乱码}{乱码}&apos;;
5. 使用单字节编码保存后对应字节：
    76 61 72 20 62 61 72 20 3D 20 27 D6 D0 CE C4 27 3B
6. 使用GBK编码读取后得到内容：
    var bar = &apos;中文&apos;;
</code></pre><p>这里的诀窍在于，不管大于0xEF的单个字节在单字节编码下被解析成什么乱码字符，使用同样的单字节编码保存这些乱码字符时，背后对应的字节保持不变。</p>
<p>NodeJS中自带了一种<code>binary</code>编码可以用来实现这个方法，因此在下例中，我们使用这种编码来演示上例对应的代码该怎么写。</p>
<pre><code>function replace(pathname) {
    var str = fs.readFileSync(pathname, &apos;binary&apos;);
    str = str.replace(&apos;foo&apos;, &apos;bar&apos;);
    fs.writeFileSync(pathname, str, &apos;binary&apos;);
}
</code></pre><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>本章介绍了使用NodeJS操作文件时需要的API以及一些技巧，总结起来有以下几点：</p>
<ul>
<li><p>学好文件操作，编写各种程序都不怕。</p>
</li>
<li><p>如果不是很在意性能，<code>fs</code>模块的同步API能让生活更加美好。</p>
</li>
<li><p>需要对文件读写做到字节级别的精细控制时，请使用<code>fs</code>模块的文件底层操作API。</p>
</li>
<li><p>不要使用拼接字符串的方式来处理路径，使用<code>path</code>模块。</p>
</li>
<li><p>掌握好目录遍历和文件编码处理技巧，很实用。</p>
</li>
</ul>

        
        </div>
        <footer class="article-footer">
            <div class="share-container">



</div>

    <a data-url="http://yoursite.com/2016/03/23/node/books/7days/03_file/" data-id="cj6oqpm1q002oyjpjvnbtc3re" class="article-share-link"><i class="fa fa-share"></i>Share</a>
<script>
    (function ($) {
        // Prevent duplicate binding
        if (typeof(__SHARE_BUTTON_BINDED__) === 'undefined' || !__SHARE_BUTTON_BINDED__) {
            __SHARE_BUTTON_BINDED__ = true;
        } else {
            return;
        }
        $('body').on('click', function() {
            $('.article-share-box.on').removeClass('on');
        }).on('click', '.article-share-link', function(e) {
            e.stopPropagation();

            var $this = $(this),
                url = $this.attr('data-url'),
                encodedUrl = encodeURIComponent(url),
                id = 'article-share-box-' + $this.attr('data-id'),
                offset = $this.offset(),
                box;

            if ($('#' + id).length) {
                box = $('#' + id);

                if (box.hasClass('on')){
                    box.removeClass('on');
                    return;
                }
            } else {
                var html = [
                    '<div id="' + id + '" class="article-share-box">',
                        '<input class="article-share-input" value="' + url + '">',
                        '<div class="article-share-links">',
                            '<a href="https://twitter.com/intent/tweet?url=' + encodedUrl + '" class="fa fa-twitter article-share-twitter" target="_blank" title="Twitter"></a>',
                            '<a href="https://www.facebook.com/sharer.php?u=' + encodedUrl + '" class="fa fa-facebook article-share-facebook" target="_blank" title="Facebook"></a>',
                            '<a href="http://pinterest.com/pin/create/button/?url=' + encodedUrl + '" class="fa fa-pinterest article-share-pinterest" target="_blank" title="Pinterest"></a>',
                            '<a href="https://plus.google.com/share?url=' + encodedUrl + '" class="fa fa-google article-share-google" target="_blank" title="Google+"></a>',
                        '</div>',
                    '</div>'
                ].join('');

              box = $(html);

              $('body').append(box);
            }

            $('.article-share-box.on').hide();

            box.css({
                top: offset.top + 25,
                left: offset.left
            }).addClass('on');

        }).on('click', '.article-share-box', function (e) {
            e.stopPropagation();
        }).on('click', '.article-share-box-input', function () {
            $(this).select();
        }).on('click', '.article-share-box-link', function (e) {
            e.preventDefault();
            e.stopPropagation();

            window.open(this.href, 'article-share-box-window-' + Date.now(), 'width=500,height=450');
        });
    })(jQuery);
</script>

            
    
        <a href="http://yoursite.com/2016/03/23/node/books/7days/03_file/#comments" class="article-comment-link disqus-comment-count" data-disqus-url="http://yoursite.com/2016/03/23/node/books/7days/03_file/">Comments</a>
    

        </footer>
    </div>
    
</article>



    <article id="post-node/books/7days/04_network" class="article article-type-post" itemscope itemprop="blogPost">
    <div class="article-inner">
        
        
        
        <div class="article-entry" itemprop="articleBody">
        
            
            <h2 id="七天学会NodeJS-网络操作"><a href="#七天学会NodeJS-网络操作" class="headerlink" title="七天学会NodeJS - 网络操作"></a>七天学会NodeJS - 网络操作</h2><p>不了解网络编程的程序员不是好前端，而NodeJS恰好提供了一扇了解网络编程的窗口。通过NodeJS，除了可以编写一些服务端程序来协助前端开发和测试外，还能够学习一些HTTP协议与Socket协议的相关知识，这些知识在优化前端性能和排查前端故障时说不定能派上用场。本章将介绍与之相关的NodeJS内置模块。</p>
<h3 id="开门红"><a href="#开门红" class="headerlink" title="开门红"></a>开门红</h3><p>NodeJS本来的用途是编写高性能Web服务器。我们首先在这里重复一下官方文档里的例子，使用NodeJS内置的<code>http</code>模块简单实现一个HTTP服务器。</p>
<pre><code>var http = require(&apos;http&apos;);

http.createServer(function (request, response) {
    response.writeHead(200, { &apos;Content-Type&apos;: &apos;text-plain&apos; });
    response.end(&apos;Hello World\n&apos;);
}).listen(8124);
</code></pre><p>以上程序创建了一个HTTP服务器并监听<code>8124</code>端口，打开浏览器访问该端口<code>http://127.0.0.1:8124/</code>就能够看到效果。</p>
<blockquote>
<p>   <strong>豆知识：</strong> 在Linux系统下，监听1024以下端口需要root权限。因此，如果想监听80或443端口的话，需要使用<code>sudo</code>命令启动程序。</p>
</blockquote>
<h3 id="API走马观花"><a href="#API走马观花" class="headerlink" title="API走马观花"></a>API走马观花</h3><p>我们先大致看看NodeJS提供了哪些和网络操作有关的API。这里并不逐一介绍每个API的使用方法，官方文档已经做得很好了。</p>
<h4 id="HTTP"><a href="#HTTP" class="headerlink" title="HTTP"></a>HTTP</h4><blockquote>
<p>   <strong>官方文档： </strong> <a href="http://nodejs.org/api/http.html" target="_blank" rel="external">http://nodejs.org/api/http.html</a></p>
</blockquote>
<p>‘http’模块提供两种使用方式：</p>
<ul>
<li><p>作为服务端使用时，创建一个HTTP服务器，监听HTTP客户端请求并返回响应。</p>
</li>
<li><p>作为客户端使用时，发起一个HTTP客户端请求，获取服务端响应。</p>
</li>
</ul>
<p>首先我们来看看服务端模式下如何工作。如开门红中的例子所示，首先需要使用<code>.createServer</code>方法创建一个服务器，然后调用<code>.listen</code>方法监听端口。之后，每当来了一个客户端请求，创建服务器时传入的回调函数就被调用一次。可以看出，这是一种事件机制。</p>
<p>HTTP请求本质上是一个数据流，由请求头（headers）和请求体（body）组成。例如以下是一个完整的HTTP请求数据内容。</p>
<pre><code>POST / HTTP/1.1
User-Agent: curl/7.26.0
Host: localhost
Accept: */*
Content-Length: 11
Content-Type: application/x-www-form-urlencoded

Hello World
</code></pre><p>可以看到，空行之上是请求头，之下是请求体。HTTP请求在发送给服务器时，可以认为是按照从头到尾的顺序一个字节一个字节地以数据流方式发送的。而<code>http</code>模块创建的HTTP服务器在接收到完整的请求头后，就会调用回调函数。在回调函数中，除了可以使用<code>request</code>对象访问请求头数据外，还能把<code>request</code>对象当作一个只读数据流来访问请求体数据。以下是一个例子。</p>
<pre><code>http.createServer(function (request, response) {
    var body = [];

    console.log(request.method);
    console.log(request.headers);

    request.on(&apos;data&apos;, function (chunk) {
        body.push(chunk);
    });

    request.on(&apos;end&apos;, function () {
        body = Buffer.concat(body);
        console.log(body.toString());
    });
}).listen(80);

------------------------------------
POST
{ &apos;user-agent&apos;: &apos;curl/7.26.0&apos;,
  host: &apos;localhost&apos;,
  accept: &apos;*/*&apos;,
  &apos;content-length&apos;: &apos;11&apos;,
  &apos;content-type&apos;: &apos;application/x-www-form-urlencoded&apos; }
Hello World
</code></pre><p>HTTP响应本质上也是一个数据流，同样由响应头（headers）和响应体（body）组成。例如以下是一个完整的HTTP请求数据内容。</p>
<pre><code>HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 11
Date: Tue, 05 Nov 2013 05:31:38 GMT
Connection: keep-alive

Hello World
</code></pre><p>在回调函数中，除了可以使用<code>response</code>对象来写入响应头数据外，还能把<code>response</code>对象当作一个只写数据流来写入响应体数据。例如在以下例子中，服务端原样将客户端请求的请求体数据返回给客户端。</p>
<pre><code>http.createServer(function (request, response) {
    response.writeHead(200, { &apos;Content-Type&apos;: &apos;text/plain&apos; });

    request.on(&apos;data&apos;, function (chunk) {
        response.write(chunk);
    });

    request.on(&apos;end&apos;, function () {
        response.end();
    });
}).listen(80);
</code></pre><p>接下来我们看看客户端模式下如何工作。为了发起一个客户端HTTP请求，我们需要指定目标服务器的位置并发送请求头和请求体，以下示例演示了具体做法。</p>
<pre><code>var options = {
        hostname: &apos;www.example.com&apos;,
        port: 80,
        path: &apos;/upload&apos;,
        method: &apos;POST&apos;,
        headers: {
            &apos;Content-Type&apos;: &apos;application/x-www-form-urlencoded&apos;
        }
    };

var request = http.request(options, function (response) {});

request.write(&apos;Hello World&apos;);
request.end();
</code></pre><p>可以看到，<code>.request</code>方法创建了一个客户端，并指定请求目标和请求头数据。之后，就可以把<code>request</code>对象当作一个只写数据流来写入请求体数据和结束请求。另外，由于HTTP请求中<code>GET</code>请求是最常见的一种，并且不需要请求体，因此<code>http</code>模块也提供了以下便捷API。</p>
<pre><code>http.get(&apos;http://www.example.com/&apos;, function (response) {});
</code></pre><p>当客户端发送请求并接收到完整的服务端响应头时，就会调用回调函数。在回调函数中，除了可以使用<code>response</code>对象访问响应头数据外，还能把<code>response</code>对象当作一个只读数据流来访问响应体数据。以下是一个例子。</p>
<pre><code>http.get(&apos;http://www.example.com/&apos;, function (response) {
    var body = [];

    console.log(response.statusCode);
    console.log(response.headers);

    response.on(&apos;data&apos;, function (chunk) {
        body.push(chunk);
    });

    response.on(&apos;end&apos;, function () {
        body = Buffer.concat(body);
        console.log(body.toString());
    });
});

------------------------------------
200
{ &apos;content-type&apos;: &apos;text/html&apos;,
  server: &apos;Apache&apos;,
  &apos;content-length&apos;: &apos;801&apos;,
  date: &apos;Tue, 05 Nov 2013 06:08:41 GMT&apos;,
  connection: &apos;keep-alive&apos; }
&lt;!DOCTYPE html&gt;
...
</code></pre><h4 id="HTTPS"><a href="#HTTPS" class="headerlink" title="HTTPS"></a>HTTPS</h4><blockquote>
<p>   <strong>官方文档： </strong> <a href="http://nodejs.org/api/https.html" target="_blank" rel="external">http://nodejs.org/api/https.html</a></p>
</blockquote>
<p><code>https</code>模块与<code>http</code>模块极为类似，区别在于<code>https</code>模块需要额外处理SSL证书。</p>
<p>在服务端模式下，创建一个HTTPS服务器的示例如下。</p>
<pre><code>var options = {
        key: fs.readFileSync(&apos;./ssl/default.key&apos;),
        cert: fs.readFileSync(&apos;./ssl/default.cer&apos;)
    };

var server = https.createServer(options, function (request, response) {
        // ...
    });
</code></pre><p>可以看到，与创建HTTP服务器相比，多了一个<code>options</code>对象，通过<code>key</code>和<code>cert</code>字段指定了HTTPS服务器使用的私钥和公钥。 </p>
<p>另外，NodeJS支持SNI技术，可以根据HTTPS客户端请求使用的域名动态使用不同的证书，因此同一个HTTPS服务器可以使用多个域名提供服务。接着上例，可以使用以下方法为HTTPS服务器添加多组证书。</p>
<pre><code>server.addContext(&apos;foo.com&apos;, {
    key: fs.readFileSync(&apos;./ssl/foo.com.key&apos;),
    cert: fs.readFileSync(&apos;./ssl/foo.com.cer&apos;)
});

server.addContext(&apos;bar.com&apos;, {
    key: fs.readFileSync(&apos;./ssl/bar.com.key&apos;),
    cert: fs.readFileSync(&apos;./ssl/bar.com.cer&apos;)
});
</code></pre><p>在客户端模式下，发起一个HTTPS客户端请求与<code>http</code>模块几乎相同，示例如下。</p>
<pre><code>var options = {
        hostname: &apos;www.example.com&apos;,
        port: 443,
        path: &apos;/&apos;,
        method: &apos;GET&apos;
    };

var request = https.request(options, function (response) {});

request.end();
</code></pre><p>但如果目标服务器使用的SSL证书是自制的，不是从颁发机构购买的，默认情况下<code>https</code>模块会拒绝连接，提示说有证书安全问题。在<code>options</code>里加入<code>rejectUnauthorized: false</code>字段可以禁用对证书有效性的检查，从而允许<code>https</code>模块请求开发环境下使用自制证书的HTTPS服务器。</p>
<h4 id="URL"><a href="#URL" class="headerlink" title="URL"></a>URL</h4><blockquote>
<p>   <strong>官方文档： </strong> <a href="http://nodejs.org/api/url.html" target="_blank" rel="external">http://nodejs.org/api/url.html</a></p>
</blockquote>
<p>处理HTTP请求时<code>url</code>模块使用率超高，因为该模块允许解析URL、生成URL，以及拼接URL。首先我们来看看一个完整的URL的各组成部分。</p>
<pre><code>                           href
 -----------------------------------------------------------------
                            host              path
                      --------------- ----------------------------
 http: // user:pass @ host.com : 8080 /p/a/t/h ?query=string #hash
 -----    ---------   --------   ---- -------- ------------- -----
protocol     auth     hostname   port pathname     search     hash
                                                ------------
                                                   query
</code></pre><p>我们可以使用<code>.parse</code>方法来将一个URL字符串转换为URL对象，示例如下。</p>
<pre><code>url.parse(&apos;http://user:pass@host.com:8080/p/a/t/h?query=string#hash&apos;);
/* =&gt;
{ protocol: &apos;http:&apos;,
  auth: &apos;user:pass&apos;,
  host: &apos;host.com:8080&apos;,
  port: &apos;8080&apos;,
  hostname: &apos;host.com&apos;,
  hash: &apos;#hash&apos;,
  search: &apos;?query=string&apos;,
  query: &apos;query=string&apos;,
  pathname: &apos;/p/a/t/h&apos;,
  path: &apos;/p/a/t/h?query=string&apos;,
  href: &apos;http://user:pass@host.com:8080/p/a/t/h?query=string#hash&apos; }
*/
</code></pre><p>传给<code>.parse</code>方法的不一定要是一个完整的URL，例如在HTTP服务器回调函数中，<code>request.url</code>不包含协议头和域名，但同样可以用<code>.parse</code>方法解析。</p>
<pre><code>http.createServer(function (request, response) {
    var tmp = request.url; // =&gt; &quot;/foo/bar?a=b&quot;
    url.parse(tmp);
    /* =&gt;
    { protocol: null,
      slashes: null,
      auth: null,
      host: null,
      port: null,
      hostname: null,
      hash: null,
      search: &apos;?a=b&apos;,
      query: &apos;a=b&apos;,
      pathname: &apos;/foo/bar&apos;,
      path: &apos;/foo/bar?a=b&apos;,
      href: &apos;/foo/bar?a=b&apos; }
    */
}).listen(80);
</code></pre><p><code>.parse</code>方法还支持第二个和第三个布尔类型可选参数。第二个参数等于<code>true</code>时，该方法返回的URL对象中，<code>query</code>字段不再是一个字符串，而是一个经过<code>querystring</code>模块转换后的参数对象。第三个参数等于<code>true</code>时，该方法可以正确解析不带协议头的URL，例如<code>//www.example.com/foo/bar</code>。</p>
<p>反过来，<code>format</code>方法允许将一个URL对象转换为URL字符串，示例如下。</p>
<pre><code>url.format({
    protocol: &apos;http:&apos;,
    host: &apos;www.example.com&apos;,
    pathname: &apos;/p/a/t/h&apos;,
    search: &apos;query=string&apos;
});
/* =&gt;
&apos;http://www.example.com/p/a/t/h?query=string&apos;
*/
</code></pre><p>另外，<code>.resolve</code>方法可以用于拼接URL，示例如下。</p>
<pre><code>url.resolve(&apos;http://www.example.com/foo/bar&apos;, &apos;../baz&apos;);
/* =&gt;
http://www.example.com/baz
*/
</code></pre><h4 id="Query-String"><a href="#Query-String" class="headerlink" title="Query String"></a>Query String</h4><blockquote>
<p>   <strong>官方文档： </strong> <a href="http://nodejs.org/api/querystring.html" target="_blank" rel="external">http://nodejs.org/api/querystring.html</a></p>
</blockquote>
<p><code>querystring</code>模块用于实现URL参数字符串与参数对象的互相转换，示例如下。</p>
<pre><code>querystring.parse(&apos;foo=bar&amp;baz=qux&amp;baz=quux&amp;corge&apos;);
/* =&gt;
{ foo: &apos;bar&apos;, baz: [&apos;qux&apos;, &apos;quux&apos;], corge: &apos;&apos; }
*/

querystring.stringify({ foo: &apos;bar&apos;, baz: [&apos;qux&apos;, &apos;quux&apos;], corge: &apos;&apos; });
/* =&gt;
&apos;foo=bar&amp;baz=qux&amp;baz=quux&amp;corge=&apos;
*/
</code></pre><h4 id="Zlib"><a href="#Zlib" class="headerlink" title="Zlib"></a>Zlib</h4><blockquote>
<p>   <strong>官方文档： </strong> <a href="http://nodejs.org/api/zlib.html" target="_blank" rel="external">http://nodejs.org/api/zlib.html</a></p>
</blockquote>
<p><code>zlib</code>模块提供了数据压缩和解压的功能。当我们处理HTTP请求和响应时，可能需要用到这个模块。</p>
<p>首先我们看一个使用<code>zlib</code>模块压缩HTTP响应体数据的例子。这个例子中，判断了客户端是否支持gzip，并在支持的情况下使用<code>zlib</code>模块返回gzip之后的响应体数据。</p>
<pre><code>http.createServer(function (request, response) {
    var i = 1024,
        data = &apos;&apos;;

    while (i--) {
        data += &apos;.&apos;;
    }

    if ((request.headers[&apos;accept-encoding&apos;] || &apos;&apos;).indexOf(&apos;gzip&apos;) !== -1) {
        zlib.gzip(data, function (err, data) {
            response.writeHead(200, {
                &apos;Content-Type&apos;: &apos;text/plain&apos;,
                &apos;Content-Encoding&apos;: &apos;gzip&apos;
            });
            response.end(data);
        });
    } else {
        response.writeHead(200, {
            &apos;Content-Type&apos;: &apos;text/plain&apos;
        });
        response.end(data);
    }
}).listen(80);
</code></pre><p>接着我们看一个使用<code>zlib</code>模块解压HTTP响应体数据的例子。这个例子中，判断了服务端响应是否使用gzip压缩，并在压缩的情况下使用<code>zlib</code>模块解压响应体数据。</p>
<pre><code>var options = {
        hostname: &apos;www.example.com&apos;,
        port: 80,
        path: &apos;/&apos;,
        method: &apos;GET&apos;,
        headers: {
            &apos;Accept-Encoding&apos;: &apos;gzip, deflate&apos;
        }
    };

http.request(options, function (response) {
    var body = [];

    response.on(&apos;data&apos;, function (chunk) {
        body.push(chunk);
    });

    response.on(&apos;end&apos;, function () {
        body = Buffer.concat(body);

        if (response.headers[&apos;content-encoding&apos;] === &apos;gzip&apos;) {
            zlib.gunzip(body, function (err, data) {
                console.log(data.toString());
            });
        } else {
            console.log(data.toString());
        }
    });
}).end();
</code></pre><h4 id="Net"><a href="#Net" class="headerlink" title="Net"></a>Net</h4><blockquote>
<p>   <strong>官方文档： </strong> <a href="http://nodejs.org/api/net.html" target="_blank" rel="external">http://nodejs.org/api/net.html</a></p>
</blockquote>
<p><code>net</code>模块可用于创建Socket服务器或Socket客户端。由于Socket在前端领域的使用范围还不是很广，这里先不涉及到WebSocket的介绍，仅仅简单演示一下如何从Socket层面来实现HTTP请求和响应。</p>
<p>首先我们来看一个使用Socket搭建一个很不严谨的HTTP服务器的例子。这个HTTP服务器不管收到啥请求，都固定返回相同的响应。</p>
<pre><code>net.createServer(function (conn) {
    conn.on(&apos;data&apos;, function (data) {
        conn.write([
            &apos;HTTP/1.1 200 OK&apos;,
            &apos;Content-Type: text/plain&apos;,
            &apos;Content-Length: 11&apos;,
            &apos;&apos;,
            &apos;Hello World&apos;
        ].join(&apos;\n&apos;));
    });
}).listen(80);
</code></pre><p>接着我们来看一个使用Socket发起HTTP客户端请求的例子。这个例子中，Socket客户端在建立连接后发送了一个HTTP GET请求，并通过<code>data</code>事件监听函数来获取服务器响应。</p>
<pre><code>var options = {
        port: 80,
        host: &apos;www.example.com&apos;
    };

var client = net.connect(options, function () {
        client.write([
            &apos;GET / HTTP/1.1&apos;,
            &apos;User-Agent: curl/7.26.0&apos;,
            &apos;Host: www.baidu.com&apos;,
            &apos;Accept: */*&apos;,
            &apos;&apos;,
            &apos;&apos;
        ].join(&apos;\n&apos;));
    });

client.on(&apos;data&apos;, function (data) {
    console.log(data.toString());
    client.end();
});
</code></pre><h3 id="灵机一点"><a href="#灵机一点" class="headerlink" title="灵机一点"></a>灵机一点</h3><p>使用NodeJS操作网络，特别是操作HTTP请求和响应时会遇到一些惊喜，这里对一些常见问题做解答。</p>
<ul>
<li><p>问： 为什么通过<code>headers</code>对象访问到的HTTP请求头或响应头字段不是驼峰的？</p>
<p>  答： 从规范上讲，HTTP请求头和响应头字段都应该是驼峰的。但现实是残酷的，不是每个HTTP服务端或客户端程序都严格遵循规范，所以NodeJS在处理从别的客户端或服务端收到的头字段时，都统一地转换为了小写字母格式，以便开发者能使用统一的方式来访问头字段，例如<code>headers[&#39;content-length&#39;]</code>。</p>
</li>
<li><p>问： 为什么<code>http</code>模块创建的HTTP服务器返回的响应是<code>chunked</code>传输方式的？</p>
<p>  答： 因为默认情况下，使用<code>.writeHead</code>方法写入响应头后，允许使用<code>.write</code>方法写入任意长度的响应体数据，并使用<code>.end</code>方法结束一个响应。由于响应体数据长度不确定，因此NodeJS自动在响应头里添加了<code>Transfer-Encoding: chunked</code>字段，并采用<code>chunked</code>传输方式。但是当响应体数据长度确定时，可使用<code>.writeHead</code>方法在响应头里加上<code>Content-Length</code>字段，这样做之后NodeJS就不会自动添加<code>Transfer-Encoding</code>字段和使用<code>chunked</code>传输方式。</p>
</li>
<li><p>问： 为什么使用<code>http</code>模块发起HTTP客户端请求时，有时候会发生<code>socket hang up</code>错误？</p>
<p>  答： 发起客户端HTTP请求前需要先创建一个客户端。<code>http</code>模块提供了一个全局客户端<code>http.globalAgent</code>，可以让我们使用<code>.request</code>或<code>.get</code>方法时不用手动创建客户端。但是全局客户端默认只允许5个并发Socket连接，当某一个时刻HTTP客户端请求创建过多，超过这个数字时，就会发生<code>socket hang up</code>错误。解决方法也很简单，通过<code>http.globalAgent.maxSockets</code>属性把这个数字改大些即可。另外，<code>https</code>模块遇到这个问题时也一样通过<code>https.globalAgent.maxSockets</code>属性来处理。</p>
</li>
</ul>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>本章介绍了使用NodeJS操作网络时需要的API以及一些坑回避技巧，总结起来有以下几点：</p>
<ul>
<li><p><code>http</code>和<code>https</code>模块支持服务端模式和客户端模式两种使用方式。</p>
</li>
<li><p><code>request</code>和<code>response</code>对象除了用于读写头数据外，都可以当作数据流来操作。</p>
</li>
<li><p><code>url.parse</code>方法加上<code>request.url</code>属性是处理HTTP请求时的固定搭配。</p>
</li>
<li><p>使用<code>zlib</code>模块可以减少使用HTTP协议时的数据传输量。</p>
</li>
<li><p>通过<code>net</code>模块的Socket服务器与客户端可对HTTP协议做底层操作。</p>
</li>
<li><p>小心踩坑。</p>
</li>
</ul>

        
        </div>
        <footer class="article-footer">
            <div class="share-container">



</div>

    <a data-url="http://yoursite.com/2016/03/23/node/books/7days/04_network/" data-id="cj6oqpm1s002pyjpjlhao2f27" class="article-share-link"><i class="fa fa-share"></i>Share</a>
<script>
    (function ($) {
        // Prevent duplicate binding
        if (typeof(__SHARE_BUTTON_BINDED__) === 'undefined' || !__SHARE_BUTTON_BINDED__) {
            __SHARE_BUTTON_BINDED__ = true;
        } else {
            return;
        }
        $('body').on('click', function() {
            $('.article-share-box.on').removeClass('on');
        }).on('click', '.article-share-link', function(e) {
            e.stopPropagation();

            var $this = $(this),
                url = $this.attr('data-url'),
                encodedUrl = encodeURIComponent(url),
                id = 'article-share-box-' + $this.attr('data-id'),
                offset = $this.offset(),
                box;

            if ($('#' + id).length) {
                box = $('#' + id);

                if (box.hasClass('on')){
                    box.removeClass('on');
                    return;
                }
            } else {
                var html = [
                    '<div id="' + id + '" class="article-share-box">',
                        '<input class="article-share-input" value="' + url + '">',
                        '<div class="article-share-links">',
                            '<a href="https://twitter.com/intent/tweet?url=' + encodedUrl + '" class="fa fa-twitter article-share-twitter" target="_blank" title="Twitter"></a>',
                            '<a href="https://www.facebook.com/sharer.php?u=' + encodedUrl + '" class="fa fa-facebook article-share-facebook" target="_blank" title="Facebook"></a>',
                            '<a href="http://pinterest.com/pin/create/button/?url=' + encodedUrl + '" class="fa fa-pinterest article-share-pinterest" target="_blank" title="Pinterest"></a>',
                            '<a href="https://plus.google.com/share?url=' + encodedUrl + '" class="fa fa-google article-share-google" target="_blank" title="Google+"></a>',
                        '</div>',
                    '</div>'
                ].join('');

              box = $(html);

              $('body').append(box);
            }

            $('.article-share-box.on').hide();

            box.css({
                top: offset.top + 25,
                left: offset.left
            }).addClass('on');

        }).on('click', '.article-share-box', function (e) {
            e.stopPropagation();
        }).on('click', '.article-share-box-input', function () {
            $(this).select();
        }).on('click', '.article-share-box-link', function (e) {
            e.preventDefault();
            e.stopPropagation();

            window.open(this.href, 'article-share-box-window-' + Date.now(), 'width=500,height=450');
        });
    })(jQuery);
</script>

            
    
        <a href="http://yoursite.com/2016/03/23/node/books/7days/04_network/#comments" class="article-comment-link disqus-comment-count" data-disqus-url="http://yoursite.com/2016/03/23/node/books/7days/04_network/">Comments</a>
    

        </footer>
    </div>
    
</article>



    <article id="post-node/books/7days/06_async_programming" class="article article-type-post" itemscope itemprop="blogPost">
    <div class="article-inner">
        
        
        
        <div class="article-entry" itemprop="articleBody">
        
            
            <h2 id="七天学会NodeJS-异步编程"><a href="#七天学会NodeJS-异步编程" class="headerlink" title="七天学会NodeJS - 异步编程"></a>七天学会NodeJS - 异步编程</h2><p>NodeJS最大的卖点——事件机制和异步IO，对开发者并不是透明的。开发者需要按异步方式编写代码才用得上这个卖点，而这一点也遭到了一些NodeJS反对者的抨击。但不管怎样，异步编程确实是NodeJS最大的特点，没有掌握异步编程就不能说是真正学会了NodeJS。本章将介绍与异步编程相关的各种知识。</p>
<h3 id="回调"><a href="#回调" class="headerlink" title="回调"></a>回调</h3><p>在代码中，异步编程的直接体现就是回调。异步编程依托于回调来实现，但不能说使用了回调后程序就异步化了。我们首先可以看看以下代码。</p>
<pre><code>function heavyCompute(n, callback) {
    var count = 0,
        i, j;

    for (i = n; i &gt; 0; --i) {
        for (j = n; j &gt; 0; --j) {
            count += 1;
        }
    }

    callback(count);
}

heavyCompute(10000, function (count) {
    console.log(count);
});

console.log(&apos;hello&apos;);

-- Console ------------------------------
100000000
hello
</code></pre><p>可以看到，以上代码中的回调函数仍然先于后续代码执行。JS本身是单线程运行的，不可能在一段代码还未结束运行时去运行别的代码，因此也就不存在异步执行的概念。</p>
<p>但是，如果某个函数做的事情是创建一个别的线程或进程，并与JS主线程并行地做一些事情，并在事情做完后通知JS主线程，那情况又不一样了。我们接着看看以下代码。</p>
<pre><code>setTimeout(function () {
    console.log(&apos;world&apos;);
}, 1000);

console.log(&apos;hello&apos;);

-- Console ------------------------------
hello
world
</code></pre><p>这次可以看到，回调函数后于后续代码执行了。如同上边所说，JS本身是单线程的，无法异步执行，因此我们可以认为<code>setTimeout</code>这类JS规范之外的由运行环境提供的特殊函数做的事情是创建一个平行线程后立即返回，让JS主进程可以接着执行后续代码，并在收到平行进程的通知后再执行回调函数。除了<code>setTimeout</code>、<code>setInterval</code>这些常见的，这类函数还包括NodeJS提供的诸如<code>fs.readFile</code>之类的异步API。</p>
<p>另外，我们仍然回到JS是单线程运行的这个事实上，这决定了JS在执行完一段代码之前无法执行包括回调函数在内的别的代码。也就是说，即使平行线程完成工作了，通知JS主线程执行回调函数了，回调函数也要等到JS主线程空闲时才能开始执行。以下就是这么一个例子。</p>
<pre><code>function heavyCompute(n) {
    var count = 0,
        i, j;

    for (i = n; i &gt; 0; --i) {
        for (j = n; j &gt; 0; --j) {
            count += 1;
        }
    }
}

var t = new Date();

setTimeout(function () {
    console.log(new Date() - t);
}, 1000);

heavyCompute(50000);

-- Console ------------------------------
8520
</code></pre><p>可以看到，本来应该在1秒后被调用的回调函数因为JS主线程忙于运行其它代码，实际执行时间被大幅延迟。</p>
<h3 id="代码设计模式"><a href="#代码设计模式" class="headerlink" title="代码设计模式"></a>代码设计模式</h3><p>异步编程有很多特有的代码设计模式，为了实现同样的功能，使用同步方式和异步方式编写的代码会有很大差异。以下分别介绍一些常见的模式。</p>
<h4 id="函数返回值"><a href="#函数返回值" class="headerlink" title="函数返回值"></a>函数返回值</h4><p>使用一个函数的输出作为另一个函数的输入是很常见的需求，在同步方式下一般按以下方式编写代码：</p>
<pre><code>var output = fn1(fn2(&apos;input&apos;));
// Do something.
</code></pre><p>而在异步方式下，由于函数执行结果不是通过返回值，而是通过回调函数传递，因此一般按以下方式编写代码：</p>
<pre><code>fn2(&apos;input&apos;, function (output2) {
    fn1(output2, function (output1) {
        // Do something.
    });
});
</code></pre><p>可以看到，这种方式就是一个回调函数套一个回调函多，套得太多了很容易写出<code>&gt;</code>形状的代码。</p>
<h4 id="遍历数组"><a href="#遍历数组" class="headerlink" title="遍历数组"></a>遍历数组</h4><p>在遍历数组时，使用某个函数依次对数据成员做一些处理也是常见的需求。如果函数是同步执行的，一般就会写出以下代码：</p>
<pre><code>var len = arr.length,
    i = 0;

for (; i &lt; len; ++i) {
    arr[i] = sync(arr[i]);
}

// All array items have processed.
</code></pre><p>如果函数是异步执行的，以上代码就无法保证循环结束后所有数组成员都处理完毕了。如果数组成员必须一个接一个串行处理，则一般按照以下方式编写异步代码：</p>
<pre><code>(function next(i, len, callback) {
    if (i &lt; len) {
        async(arr[i], function (value) {
            arr[i] = value;
            next(i + 1, len, callback);
        });
    } else {
        callback();
    }
}(0, arr.length, function () {
    // All array items have processed.
}));
</code></pre><p>可以看到，以上代码在异步函数执行一次并返回执行结果后才传入下一个数组成员并开始下一轮执行，直到所有数组成员处理完毕后，通过回调的方式触发后续代码的执行。</p>
<p>如果数组成员可以并行处理，但后续代码仍然需要所有数组成员处理完毕后才能执行的话，则异步代码会调整成以下形式：</p>
<pre><code>(function (i, len, count, callback) {
    for (; i &lt; len; ++i) {
        (function (i) {
            async(arr[i], function (value) {
                arr[i] = value;
                if (++count === len) {
                    callback();
                }
            });
        }(i));
    }
}(0, arr.length, 0, function () {
    // All array items have processed.
}));
</code></pre><p>可以看到，与异步串行遍历的版本相比，以上代码并行处理所有数组成员，并通过计数器变量来判断什么时候所有数组成员都处理完毕了。</p>
<h4 id="异常处理"><a href="#异常处理" class="headerlink" title="异常处理"></a>异常处理</h4><p>JS自身提供的异常捕获和处理机制——<code>try..catch..</code>，只能用于同步执行的代码。以下是一个例子。</p>
<pre><code>function sync(fn) {
    return fn();
}

try {
    sync(null);
    // Do something.
} catch (err) {
    console.log(&apos;Error: %s&apos;, err.message);
}

-- Console ------------------------------
Error: object is not a function
</code></pre><p>可以看到，异常会沿着代码执行路径一直冒泡，直到遇到第一个<code>try</code>语句时被捕获住。但由于异步函数会打断代码执行路径，异步函数执行过程中以及执行之后产生的异常冒泡到执行路径被打断的位置时，如果一直没有遇到<code>try</code>语句，就作为一个全局异常抛出。以下是一个例子。</p>
<pre><code>function async(fn, callback) {
    // Code execution path breaks here.
    setTimeout(function ()　{
        callback(fn());
    }, 0);
}

try {
    async(null, function (data) {
        // Do something.
    });
} catch (err) {
    console.log(&apos;Error: %s&apos;, err.message);
}

-- Console ------------------------------
/home/user/test.js:4
        callback(fn());
                 ^
TypeError: object is not a function
    at null._onTimeout (/home/user/test.js:4:13)
    at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
</code></pre><p>因为代码执行路径被打断了，我们就需要在异常冒泡到断点之前用<code>try</code>语句把异常捕获住，并通过回调函数传递被捕获的异常。于是我们可以像下边这样改造上边的例子。</p>
<pre><code>function async(fn, callback) {
    // Code execution path breaks here.
    setTimeout(function ()　{
        try {
            callback(null, fn());
        } catch (err) {
            callback(err);
        }
    }, 0);
}

async(null, function (err, data) {
    if (err) {
        console.log(&apos;Error: %s&apos;, err.message);
    } else {
        // Do something.
    }
});

-- Console ------------------------------
Error: object is not a function
</code></pre><p>可以看到，异常再次被捕获住了。在NodeJS中，几乎所有异步API都按照以上方式设计，回调函数中第一个参数都是<code>err</code>。因此我们在编写自己的异步函数时，也可以按照这种方式来处理异常，与NodeJS的设计风格保持一致。</p>
<p>有了异常处理方式后，我们接着可以想一想一般我们是怎么写代码的。基本上，我们的代码都是做一些事情，然后调用一个函数，然后再做一些事情，然后再调用一个函数，如此循环。如果我们写的是同步代码，只需要在代码入口点写一个<code>try</code>语句就能捕获所有冒泡上来的异常，示例如下。</p>
<pre><code>function main() {
    // Do something.
    syncA();
    // Do something.
    syncB();
    // Do something.
    syncC();
}

try {
    main();
} catch (err) {
    // Deal with exception.
}
</code></pre><p>但是，如果我们写的是异步代码，就只有呵呵了。由于每次异步函数调用都会打断代码执行路径，只能通过回调函数来传递异常，于是我们就需要在每个回调函数里判断是否有异常发生，于是只用三次异步函数调用，就会产生下边这种代码。</p>
<pre><code>function main(callback) {
    // Do something.
    asyncA(function (err, data) {
        if (err) {
            callback(err);
        } else {
            // Do something
            asyncB(function (err, data) {
                if (err) {
                    callback(err);
                } else {
                    // Do something
                    asyncC(function (err, data) {
                        if (err) {
                            callback(err);
                        } else {
                            // Do something
                            callback(null);
                        }
                    });
                }
            });
        }
    });
}

main(function (err) {
    if (err) {
        // Deal with exception.
    }
});
</code></pre><p>可以看到，回调函数已经让代码变得复杂了，而异步方式下对异常的处理更加剧了代码的复杂度。如果NodeJS的最大卖点最后变成这个样子，那就没人愿意用NodeJS了，因此接下来会介绍NodeJS提供的一些解决方案。</p>
<h3 id="域（Domain）"><a href="#域（Domain）" class="headerlink" title="域（Domain）"></a>域（Domain）</h3><blockquote>
<p>   <strong>官方文档： </strong> <a href="http://nodejs.org/api/domain.html" target="_blank" rel="external">http://nodejs.org/api/domain.html</a></p>
</blockquote>
<p>NodeJS提供了<code>domain</code>模块，可以简化异步代码的异常处理。在介绍该模块之前，我们需要首先理解“域”的概念。简单的讲，一个域就是一个JS运行环境，在一个运行环境中，如果一个异常没有被捕获，将作为一个全局异常被抛出。NodeJS通过<code>process</code>对象提供了捕获全局异常的方法，示例代码如下</p>
<pre><code>process.on(&apos;uncaughtException&apos;, function (err) {
    console.log(&apos;Error: %s&apos;, err.message);
});

setTimeout(function (fn) {
    fn();
});

-- Console ------------------------------
Error: undefined is not a function
</code></pre><p>虽然全局异常有个地方可以捕获了，但是对于大多数异常，我们希望尽早捕获，并根据结果决定代码的执行路径。我们用以下HTTP服务器代码作为例子：</p>
<pre><code>function async(request, callback) {
    // Do something.
    asyncA(request, function (err, data) {
        if (err) {
            callback(err);
        } else {
            // Do something
            asyncB(request, function (err, data) {
                if (err) {
                    callback(err);
                } else {
                    // Do something
                    asyncC(request, function (err, data) {
                        if (err) {
                            callback(err);
                        } else {
                            // Do something
                            callback(null, data);
                        }
                    });
                }
            });
        }
    });
}

http.createServer(function (request, response) {
    async(request, function (err, data) {
        if (err) {
            response.writeHead(500);
            response.end();
        } else {
            response.writeHead(200);
            response.end(data);
        }
    });
});
</code></pre><p>以上代码将请求对象交给异步函数处理后，再根据处理结果返回响应。这里采用了使用回调函数传递异常的方案，因此<code>async</code>函数内部如果再多几个异步函数调用的话，代码就变成上边这副鬼样子了。为了让代码好看点，我们可以在每处理一个请求时，使用<code>domain</code>模块创建一个子域（JS子运行环境）。在子域内运行的代码可以随意抛出异常，而这些异常可以通过子域对象的<code>error</code>事件统一捕获。于是以上代码可以做如下改造：</p>
<pre><code>function async(request, callback) {
    // Do something.
    asyncA(request, function (data) {
        // Do something
        asyncB(request, function (data) {
            // Do something
            asyncC(request, function (data) {
                // Do something
                callback(data);
            });
        });
    });
}

http.createServer(function (request, response) {
    var d = domain.create();

    d.on(&apos;error&apos;, function () {
        response.writeHead(500);
        response.end();
    });

    d.run(function () {
        async(request, function (data) {
            response.writeHead(200);
            response.end(data);
        });
    });
});
</code></pre><p>可以看到，我们使用<code>.create</code>方法创建了一个子域对象，并通过<code>.run</code>方法进入需要在子域中运行的代码的入口点。而位于子域中的异步函数回调函数由于不再需要捕获异常，代码一下子瘦身很多。</p>
<h4 id="陷阱"><a href="#陷阱" class="headerlink" title="陷阱"></a>陷阱</h4><p>无论是通过<code>process</code>对象的<code>uncaughtException</code>事件捕获到全局异常，还是通过子域对象的<code>error</code>事件捕获到了子域异常，在NodeJS官方文档里都强烈建议处理完异常后立即重启程序，而不是让程序继续运行。按照官方文档的说法，发生异常后的程序处于一个不确定的运行状态，如果不立即退出的话，程序可能会发生严重内存泄漏，也可能表现得很奇怪。</p>
<p>但这里需要澄清一些事实。JS本身的<code>throw..try..catch</code>异常处理机制并不会导致内存泄漏，也不会让程序的执行结果出乎意料，但NodeJS并不是存粹的JS。NodeJS里大量的API内部是用C/C++实现的，因此NodeJS程序的运行过程中，代码执行路径穿梭于JS引擎内部和外部，而JS的异常抛出机制可能会打断正常的代码执行流程，导致C/C++部分的代码表现异常，进而导致内存泄漏等问题。</p>
<p>因此，使用<code>uncaughtException</code>或<code>domain</code>捕获异常，代码执行路径里涉及到了C/C++部分的代码时，如果不能确定是否会导致内存泄漏等问题，最好在处理完异常后重启程序比较妥当。而使用<code>try</code>语句捕获异常时一般捕获到的都是JS本身的异常，不用担心上诉问题。</p>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>本章介绍了JS异步编程相关的知识，总结起来有以下几点：</p>
<ul>
<li><p>不掌握异步编程就不算学会NodeJS。</p>
</li>
<li><p>异步编程依托于回调来实现，而使用回调不一定就是异步编程。</p>
</li>
<li><p>异步编程下的函数间数据传递、数组遍历和异常处理与同步编程有很大差别。</p>
</li>
<li><p>使用<code>domain</code>模块简化异步代码的异常处理，并小心陷阱。</p>
</li>
</ul>

        
        </div>
        <footer class="article-footer">
            <div class="share-container">



</div>

    <a data-url="http://yoursite.com/2016/03/23/node/books/7days/06_async_programming/" data-id="cj6oqpm1t002qyjpjdz8w935v" class="article-share-link"><i class="fa fa-share"></i>Share</a>
<script>
    (function ($) {
        // Prevent duplicate binding
        if (typeof(__SHARE_BUTTON_BINDED__) === 'undefined' || !__SHARE_BUTTON_BINDED__) {
            __SHARE_BUTTON_BINDED__ = true;
        } else {
            return;
        }
        $('body').on('click', function() {
            $('.article-share-box.on').removeClass('on');
        }).on('click', '.article-share-link', function(e) {
            e.stopPropagation();

            var $this = $(this),
                url = $this.attr('data-url'),
                encodedUrl = encodeURIComponent(url),
                id = 'article-share-box-' + $this.attr('data-id'),
                offset = $this.offset(),
                box;

            if ($('#' + id).length) {
                box = $('#' + id);

                if (box.hasClass('on')){
                    box.removeClass('on');
                    return;
                }
            } else {
                var html = [
                    '<div id="' + id + '" class="article-share-box">',
                        '<input class="article-share-input" value="' + url + '">',
                        '<div class="article-share-links">',
                            '<a href="https://twitter.com/intent/tweet?url=' + encodedUrl + '" class="fa fa-twitter article-share-twitter" target="_blank" title="Twitter"></a>',
                            '<a href="https://www.facebook.com/sharer.php?u=' + encodedUrl + '" class="fa fa-facebook article-share-facebook" target="_blank" title="Facebook"></a>',
                            '<a href="http://pinterest.com/pin/create/button/?url=' + encodedUrl + '" class="fa fa-pinterest article-share-pinterest" target="_blank" title="Pinterest"></a>',
                            '<a href="https://plus.google.com/share?url=' + encodedUrl + '" class="fa fa-google article-share-google" target="_blank" title="Google+"></a>',
                        '</div>',
                    '</div>'
                ].join('');

              box = $(html);

              $('body').append(box);
            }

            $('.article-share-box.on').hide();

            box.css({
                top: offset.top + 25,
                left: offset.left
            }).addClass('on');

        }).on('click', '.article-share-box', function (e) {
            e.stopPropagation();
        }).on('click', '.article-share-box-input', function () {
            $(this).select();
        }).on('click', '.article-share-box-link', function (e) {
            e.preventDefault();
            e.stopPropagation();

            window.open(this.href, 'article-share-box-window-' + Date.now(), 'width=500,height=450');
        });
    })(jQuery);
</script>

            
    
        <a href="http://yoursite.com/2016/03/23/node/books/7days/06_async_programming/#comments" class="article-comment-link disqus-comment-count" data-disqus-url="http://yoursite.com/2016/03/23/node/books/7days/06_async_programming/">Comments</a>
    

        </footer>
    </div>
    
</article>



    <nav id="page-nav">
        <a class="extend prev" rel="prev" href="/">&laquo; Prev</a><a class="page-number" href="/">1</a><span class="page-number current">2</span><a class="page-number" href="/page/3/">3</a><a class="extend next" rel="next" href="/page/3/">Next &raquo;</a>
    </nav>
</section>
            
                
<aside id="sidebar">
   
        
    <div class="widget-wrap">
        <h3 class="widget-title">recent</h3>
        <div class="widget">
            <ul id="recent-post" class="">
                
                    <li>
                        
                        <div class="item-thumbnail">
                            <a href="/2017/08/23/predator/" class="thumbnail">
    
    
        <span class="thumbnail-image thumbnail-none"></span>
    
</a>

                        </div>
                        
                        <div class="item-inner">
                            <p class="item-category"></p>
                            <p class="item-title"><a href="/2017/08/23/predator/" class="title"></a></p>
                            <p class="item-date"><time datetime="2017-08-23T02:50:51.000Z" itemprop="datePublished">2017-08-23</time></p>
                        </div>
                    </li>
                
                    <li>
                        
                        <div class="item-thumbnail">
                            <a href="/2017/08/23/node/node_three_libraies/" class="thumbnail">
    
    
        <span class="thumbnail-image thumbnail-none"></span>
    
</a>

                        </div>
                        
                        <div class="item-inner">
                            <p class="item-category"></p>
                            <p class="item-title"><a href="/2017/08/23/node/node_three_libraies/" class="title"></a></p>
                            <p class="item-date"><time datetime="2017-08-23T02:16:21.000Z" itemprop="datePublished">2017-08-23</time></p>
                        </div>
                    </li>
                
                    <li>
                        
                        <div class="item-thumbnail">
                            <a href="/2017/08/22/mysql/mac导出:导入mysql/" class="thumbnail">
    
    
        <span class="thumbnail-image thumbnail-none"></span>
    
</a>

                        </div>
                        
                        <div class="item-inner">
                            <p class="item-category"><a class="article-category-link" href="/categories/mysql/">mysql</a></p>
                            <p class="item-title"><a href="/2017/08/22/mysql/mac导出:导入mysql/" class="title">mac导出/导入mysql</a></p>
                            <p class="item-date"><time datetime="2017-08-22T11:26:44.000Z" itemprop="datePublished">2017-08-22</time></p>
                        </div>
                    </li>
                
                    <li>
                        
                        <div class="item-thumbnail">
                            <a href="/2017/08/18/linux/vim的常用命令/" class="thumbnail">
    
    
        <span class="thumbnail-image thumbnail-none"></span>
    
</a>

                        </div>
                        
                        <div class="item-inner">
                            <p class="item-category"><a class="article-category-link" href="/categories/linux/">linux</a><i class="fa fa-angle-right"></i><a class="article-category-link" href="/categories/linux/vim/">vim</a></p>
                            <p class="item-title"><a href="/2017/08/18/linux/vim的常用命令/" class="title">vim的常用命令</a></p>
                            <p class="item-date"><time datetime="2017-08-18T06:59:13.000Z" itemprop="datePublished">2017-08-18</time></p>
                        </div>
                    </li>
                
                    <li>
                        
                        <div class="item-thumbnail">
                            <a href="/2017/07/19/git/git commit 编写风格模板/" class="thumbnail">
    
    
        <span class="thumbnail-image thumbnail-none"></span>
    
</a>

                        </div>
                        
                        <div class="item-inner">
                            <p class="item-category"><a class="article-category-link" href="/categories/git/">git</a></p>
                            <p class="item-title"><a href="/2017/07/19/git/git commit 编写风格模板/" class="title">git commit 编写风格模板</a></p>
                            <p class="item-date"><time datetime="2017-07-19T12:15:00.000Z" itemprop="datePublished">2017-07-19</time></p>
                        </div>
                    </li>
                
            </ul>
        </div>
    </div>

    
        
    <div class="widget-wrap">
        <h3 class="widget-title">categories</h3>
        <div class="widget">
            <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/android/">android</a><span class="category-list-count">7</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/android/books/">books</a><span class="category-list-count">3</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/android/books/《Android开发艺术探索》/">《Android开发艺术探索》</a><span class="category-list-count">3</span></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/android/code/">code</a><span class="category-list-count">4</span></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/git/">git</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/learn/">learn</a><span class="category-list-count">1</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/learn/books/">books</a><span class="category-list-count">1</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/learn/books/《如何高效学习》/">《如何高效学习》</a><span class="category-list-count">1</span></li></ul></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/linux/">linux</a><span class="category-list-count">1</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/linux/vim/">vim</a><span class="category-list-count">1</span></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/mysql/">mysql</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/node/">node</a><span class="category-list-count">1</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/node/books/">books</a><span class="category-list-count">1</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/node/books/7days/">7days</a><span class="category-list-count">1</span></li></ul></li></ul></li></ul>
        </div>
    </div>

    
        
    <div class="widget-wrap">
        <h3 class="widget-title">archives</h3>
        <div class="widget">
            <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/08/">August 2017</a><span class="archive-list-count">4</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/07/">July 2017</a><span class="archive-list-count">1</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/04/">April 2017</a><span class="archive-list-count">2</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/03/">March 2017</a><span class="archive-list-count">8</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/03/">March 2016</a><span class="archive-list-count">7</span></li></ul>
        </div>
    </div>

    
        
    <div class="widget-wrap">
        <h3 class="widget-title">tags</h3>
        <div class="widget">
            <ul class="tag-list"><li class="tag-list-item"><a class="tag-list-link" href="/tags/7days/">7days</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/android/">android</a><span class="tag-list-count">7</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/books/">books</a><span class="tag-list-count">5</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/code/">code</a><span class="tag-list-count">4</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/git/">git</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/learn/">learn</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/mysql/">mysql</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/node/">node</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/vim/">vim</a><span class="tag-list-count">1</span></li></ul>
        </div>
    </div>

    
        
    <div class="widget-wrap">
        <h3 class="widget-title">tag cloud</h3>
        <div class="widget tagcloud">
            <a href="/tags/7days/" style="font-size: 10px;">7days</a> <a href="/tags/android/" style="font-size: 20px;">android</a> <a href="/tags/books/" style="font-size: 16.67px;">books</a> <a href="/tags/code/" style="font-size: 13.33px;">code</a> <a href="/tags/git/" style="font-size: 10px;">git</a> <a href="/tags/learn/" style="font-size: 10px;">learn</a> <a href="/tags/mysql/" style="font-size: 10px;">mysql</a> <a href="/tags/node/" style="font-size: 10px;">node</a> <a href="/tags/vim/" style="font-size: 10px;">vim</a>
        </div>
    </div>

    
        
    <div class="widget-wrap widget-list">
        <h3 class="widget-title">links</h3>
        <div class="widget">
            <ul>
                
                    <li>
                        <a href="http://hexo.io">Hexo</a>
                    </li>
                
            </ul>
        </div>
    </div>


    
    <div id="toTop" class="fa fa-angle-up"></div>
</aside>

            
        </div>
        <footer id="footer">
    <div class="outer">
        <div id="footer-info" class="inner">
            &copy; 2017 Ernest Chang<br>
            Powered by <a href="http://hexo.io/" target="_blank">Hexo</a>. Theme by <a href="http://github.com/ppoffice">PPOffice</a>
        </div>
    </div>
</footer>
        
    
    <script>
    var disqus_config = function () {
        
        this.page.identifier = '';
    };
    (function() { 
        var d = document, s = d.createElement('script');  
        s.src = '//' + 'hexo-theme-icarus' + '.disqus.com/count.js';
        s.setAttribute('data-timestamp', +new Date());
        (d.head || d.body).appendChild(s);
    })();
</script>




    
        <script src="/libs/lightgallery/js/lightgallery.min.js"></script>
        <script src="/libs/lightgallery/js/lg-thumbnail.min.js"></script>
        <script src="/libs/lightgallery/js/lg-pager.min.js"></script>
        <script src="/libs/lightgallery/js/lg-autoplay.min.js"></script>
        <script src="/libs/lightgallery/js/lg-fullscreen.min.js"></script>
        <script src="/libs/lightgallery/js/lg-zoom.min.js"></script>
        <script src="/libs/lightgallery/js/lg-hash.min.js"></script>
        <script src="/libs/lightgallery/js/lg-share.min.js"></script>
        <script src="/libs/lightgallery/js/lg-video.min.js"></script>
    
    
        <script src="/libs/justified-gallery/jquery.justifiedGallery.min.js"></script>
    
    



<!-- Custom Scripts -->
<script src="/js/main.js"></script>

    </div>
</body>
</html>